# vuejs tutorial ![vue-logo](img/logo.png) ## 搭建案例演示自動刷新環境 創建一個 `package.josn` 文件: ```bashnpm init -y``` 安裝 `browser-sync`: ```bash# npm install --sav ...
# vuejs tutorial
![vue-logo](img/logo.png)
---
## 搭建案例演示自動刷新環境
創建一個 `package.josn` 文件:
```bash
npm init -y
```
安裝 `browser-sync`:
```bash
# npm install --save-dev browser-sync
# 將 browser-sync 包保存到開發依賴中
# 就可以執行 npm install 的時候加入 --production 選項不安裝這個包
npm i -D browser-sync
```
在 package.json 文件中加入以下內容:
```json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "browser-sync start --server --directory --files \"code/*.html\""
}
```
打開終端,啟動開發預覽服務:
```bash
npm start
```
---
## vuejs 介紹
---
## 安裝
- Vue.js 不支持 IE8 及其以下版本,因為 Vue.js 使用了 IE8 不能模擬的 ECMAScript 5 特性
- 每個版本的更新日誌見:https://github.com/vuejs/vue/releases
- 獨立 js 文件
+ 開發版本(未壓縮):http://vuejs.org/js/vue.js
+ 生產版本(壓縮):http://vuejs.org/js/vue.min.js
- CDN:https://unpkg.com/vue
- NPM: `npm install vue`
- Bower: `bower install vue`
- 相容 AMD 規範
+ 獨立下載版本或通過 Bower 安裝的版本已用 UMD 包裝,因此它們可以直接用作 AMD 模塊。
---
## Vue 實例
- 每個 Vue.js 應用都是通過構造函數 Vue 創建的
- 在實例化 Vue 時,需要傳入一個選項對象,它可以包含數據、模板、掛載元素、方法、生命周期鉤子等選項
+ data: 屬性數據
+ computed: 計算屬性
+ methods: 方法
+ el: 掛載點
+ directives: 自定義指令
+ filters: 自定義過濾器
+ ...
+ 全部選項可以在 API 文檔中查看:https://cn.vuejs.org/v2/api/
- 實例選項:data
+ https://cn.vuejs.org/v2/guide/instance.html#屬性與方法
+ [選項/數據 - data](https://cn.vuejs.org/v2/api/#data)
+ [深入響應式原理](https://cn.vuejs.org/v2/guide/reactivity.html)
+ 作用:根據視圖抽象數據模型
+ 註意:視圖中使用的數據必須在 data 中初始化
+ 每個 VUe 實例都會代理其 data 對象里所有的屬性
* 也可以通過 vue 實例的 $data 屬性訪問 data 中的數據
* 建議:如果要使用 vm 讀取或修改 data 中的數據,建議加上 vm.$data 去訪問
+ 只有被初始代理的屬性是響應式的
* 如果是在實例創建之後添加新的屬性到實例上,它不會觸發視圖更新
+ Vue 不允許在已經創建的實例上動態添加新的根級響應式屬性
- Vue 不能檢測到對象屬性的動態添加或刪除
+ 也就是說動態添加或刪除的對象屬性不是響應式的
+ 如果希望動態添加和刪除對象的屬性是響應式的則需要通過:
* `Vue.set( object, key, value )`
* 或 `vm.$set( object, key, value )`
+ 如果刪除對象屬性是響應式的:
* `Vue.delete( object, key )`
* 或 `vm.$delete( object, key )`
- 實例選項:methods
+ https://cn.vuejs.org/v2/api/#methods
+ 作用:為視圖交互提供行為函數
+ 可以在行為函數中通過 `this` 訪問 data 中的數據成員
+ 註意:methods 中的行為函數不要寫箭頭函數
* 因為這樣會改變內部 this 的指向
- 實例屬性
+ https://cn.vuejs.org/v2/api/#實例屬性
+ $data
* Vue 實例觀察的數據對象。Vue 實例代理了對其 data 對象屬性的訪問。
+ $el
* Vue 實例使用的根 DOM 元素
- 實例方法/數據
+ https://cn.vuejs.org/v2/api/#實例方法-數據
+ $watch
+ $set Vue.set 的別名
+ $delete Vue.delete 的別名
---
## 模板語法
### 插值
#### 文本
- 響應插值:
+ `<span>Message: {{ msg }}</span>`
+ 註意: Mustache 語法不能在 HTML 屬性中使用,應使用 `v-bind` 指令
- 一次性插值:
+ `<span v-once>This will never change: {{ msg }}</span>`
+ 註意:會影響該節點及內部節點所有的綁定
#### 純 HTML
雙大括弧會將數據解釋為純文本,而非 HTML 。為了輸出真正的 HTML ,你需要使用 v-html 指令:
```html
<div v-html="rawHtml"></div>
```
- 為什麼不直接輸出 HTML
- 什麼是 XSS 攻擊:跨站腳本攻擊
+ 後天或者後後天補課
#### 屬性
**註意:Mustache 不能在 HTML 屬性中使用,應使用 v-bind 指令:**
```html
<div v-bind:id="dynamicId"></div>
```
這對布爾值的屬性也有效 —— 如果條件被求值為 false 的話該屬性會被移除:
```html
<button v-bind:disabled="someDynamicCondition">Button</button>
```
#### 使用 JavaScript 表達式
Vue.js 都提供了完全的 JavaScript 表達式支持:
```html
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
```
這些表達式會在所屬 Vue 實例的數據作用域下作為 JavaScript 被解析。
有個限制就是,每個綁定都只能包含單個表達式,所以下麵的例子都不會生效:
```html
<!-- 這是語句,不是表達式 -->
{{ var a = 1 }}
<!-- 流控制也不會生效,請使用三元表達式 -->
{{ if (ok) { return message } }}
```
### 指令
### Vue 內置指令
- v-text
+ 和 {{}} 效果是一樣
+ 但是 {{}} 會閃爍
+ 解決方法就是利用 v-text 綁定數據
+ 如果又想用 {{}}} 還需要避免閃爍
+ 使用 v-cloak 處理
- v-html
+ 預設 {{}} 和 v-text 會把 html 格式字元串原樣輸出
+ 可以使用 v-html 將 html 格式字元串作為 HTML 渲染到節點中
- v-show
+ 是否顯示和隱藏
- v-if
+ 是否渲染和移除
- v-else
+ v-if 的 else 塊
- v-else-if
+ 是 v-if 的邏輯塊
+ 同樣的,也必須緊跟著 v-if
- v-for
+ 迴圈遍歷輸出
- v-on
+ DOM 元素的事件綁定
+ 例如:`v-on:click`、`v-on:blur`
- v-bind
+ 動態綁定 HTML 屬性
+ 例如:`v-bind:title`、`v-bind:class`
- v-model
+ 和表單控制項進行雙向數據綁定
- v-pre
+ 不處理指定節點及內部所有節點的 vue 規則
+ 例如可以用來顯示原始的 Mustache 標簽
+ 作用:跳過大量沒有指令的節點可以加快編譯速度
- v-cloak
+ 可以處理表達式閃爍的問題
- v-once
+ 一次性綁定,後續數據的更新不會響應
指令(Directives)是帶有 `v-` 首碼的特殊屬性。
指令屬性的值預期是單一 JavaScript 表達式(除了 `v-for`,之後再討論)。指令的職責就是當其表達式的值改變時相應地將某些行為應用到 DOM 上。
```html
<p v-if="seen">Now you see me</p>
```
這裡, v-if 指令將根據表達式 seen 的值的真假來移除/插入 <p> 元素。
#### 參數
一些指令能接受一個“參數”,在指令後以冒號指明。
例如, v-bind 指令被用來響應地更新 HTML 屬性:
```html
<a v-bind:href="url"></a>
```
在這裡 `href` 是參數,告知 `v-bind` 指令將該元素的 `href` 屬性與表達式 `url` 的值綁定。
另一個例子是 v-on 指令,它用於監聽 DOM 事件:
```html
<a v-on:click="doSomething">
```
在這裡參數是監聽的事件名:`click`。
#### 修飾符
修飾符(Modifiers)是以半形句號 . 指明的特殊尾碼,用於指出一個指令應該以特殊方式綁定。
例如,.prevent 修飾符告訴 v-on 指令對於觸發的事件調用 event.preventDefault():
```html
<div>
<input type="text" v-on:keyup.enter="xxx">
</div>
```
```html
<form v-on:submit.prevent="onSubmit"></form>
<input type="text" v-on:keyup.enter="addTodo">
```
### 過濾器
> 註意:Vue 2.x 中,過濾器只能在 mustache 綁定和 v-bind 表達式(從 2.1.0 開始支持)中使用,
> 因為過濾器設計目的就是用於文本轉換。為了在其他指令中實現更複雜的數據變換,你應該使用 **計算屬性**。
- Vue.js 允許你自定義過濾器,可被用作一些常見的文本格式化
- 過濾器可以用在兩個地方:`mustache 插值` 和 `v-bind 表達式`
全局過濾器:
```js
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
```
局部過濾器:
```js
new Vue({
// ...
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})
```
過濾器使用格式:
```html
<!-- in mustaches -->
{{ message | capitalize }}
<!-- in v-bind -->
<div v-bind:id="rawId | formatId"></div>
```
過濾器可以串聯:
```html
{{ message | filterA | filterB }}
```
過濾器是 JavaScript 函數,因此可以接受參數:
```html
{{ message | filterA('arg1', arg2) }}
```
這裡,字元串 'arg1' 將傳給過濾器作為第二個參數,arg2 表達式的值將被求值然後傳給過濾器作為第三個參數。
### 縮寫
#### v-bind 縮寫
```html
<!-- 完整語法 -->
<a v-bind:href="url"></a>
<!-- 縮寫 -->
<a :href="url"></a>
```
#### v-on 縮寫
```html
<!-- 完整語法 -->
<a v-on:click="doSomething"></a>
<!-- 縮寫 -->
<a @click="doSomething"></a>
```
---
## 計算屬性
模板內的表達式是非常便利的,但是它們實際上只用於簡單的運算。
在模板中放入太多的邏輯會讓模板過重且難以維護。例如:
```html
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
```
在這種情況下,模板不再簡單和清晰。
這就是對於任何複雜邏輯,你都應當使用計算屬性的原因。
#### 基礎例子:反轉字元串
```js
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// a computed getter
reversedMessage: function () {
// `this` points to the vm instance
return this.message.split('').reverse().join('')
}
}
})
```
你可以像綁定普通屬性一樣在模板中綁定計算屬性。
Vue 知道 `vm.reversedMessage` 依賴於 `vm.message` ,
因此當 `vm.message` 發生改變時,所有依賴於 `vm.reversedMessage` 的綁定也會被重新計算進行更新。
---
## Class 與 Style 綁定
在 `v-bind` 用於 `class` 和 `style` 時, Vue.js 專門增強了它。
表達式的結果類型除了 **字元串** 之外,還可以是 **對象** 或 **數組** 。
### 綁定 HTML Class
#### 對象語法
```html
<div v-bind:class="{ active: isActive }"></div>
<!-- v-bind:class 指令可以與普通的 class 屬性共存 -->
<div class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }">
</div>
```
也可以直接綁定數據里的一個對象:
```html
<div v-bind:class="classObject"></div>
<script>
new Vue({
data: {
classObject: {
active: true,
'text-danger': false
}
}
})
</script>
```
#### 數組語法
```html
<!-- 可以把一個數組傳給 v-bind:class ,以應用一個 class 列表 -->
<div v-bind:class="[activeClass, errorClass]">
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
<!-- 根據條件切換列表中的 class ,可以用三元表達式: -->
<div v-bind:class="[isActive ? activeClass : '', errorClass]">
<!-- 可以在數組語法中使用對象語法: -->
<div v-bind:class="[{ active: isActive }, errorClass]">
```
### 綁定內聯樣式
```html
<!-- CSS 屬性名可以用駝峰式(camelCase)或名短橫分隔命(kebab-case) -->
<div v-bind:style="{ color: activeColor, 'font-size': fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
<!-- 直接綁定到一個樣式對象 -->
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
<!-- v-bind:style 的數組語法可以將多個樣式對象應用到一個元素上 -->
<div v-bind:style="[baseStyles, overridingStyles]">
```
---
## 條件渲染
### v-if-else-elseif
```html
<!-- 基本用法 -->
<h1 v-if="ok">Yes</h1>
<!--
通過 template 包裝多個元素,渲染結果不包含 template
v-else 元素必須緊跟在 v-if 或者 v-else-if 元素的後面——否則它將不會被識別。
-->
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
<!-- 使用 v-else 指令來表示 v-if 的“else 塊” -->
<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>
<!--
v-else-if,顧名思義,充當 v-if 的“else-if 塊”。可以鏈式地使用多次:
v-else,,v-else-if 必須緊跟在 v-if 或者 v-else-if 元素之後
-->
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
```
---
## 列表渲染
---
## 事件處理器
---
## 表單控制項綁定
---
## 組件
組件是 Vue.js 最強大的功能,組件可以擴展自定義 HTML 元素,封裝可重用代碼。
### 組件的命名
- 如果一個單詞就只寫一個單詞即可
- 如果是多個單片語成的名字
+ 建議使用短橫杠的方式
- 如果使用的是駝峰命名
+ 則在 DOM 模板中必須將 駝峰命名的組件名改為短橫杠的方式
+ 在 字元串模板中,無論是駝峰還是短橫杠都行
### 組件基礎
- 註冊全局組件
- 註冊局部組件
- 組件的模板
- 組件的 data
#### 註冊全局組件:`Vue.component(tagName, options)`
註冊:
```js
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
```
使用:
```html
<div id="example">
<my-component></my-component>
</div>
```
渲染為:
```html
<div id="example">
<div>A custom component!</div>
</div>
```
#### 組件的 template
組件的 template 必須具有一個根節點,否則,模板編譯報錯。
- 可以是內聯模板
- 可以是 script 標簽模板
- 可以是 .vue 模板
#### 局部註冊組件:實例選項的 `components`
不必在全局註冊每個組件。
通過使用組件實例選項註冊,可以使組件僅在另一個實例/組件的作用域中可用:
```html
<body>
<div id="app">
<!-- 渲染為 <div>局部組件</div> -->
<my-component></my-component>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
components: {
'my-component': {
template: '<div>局部組件</div>'
}
},
data: {
},
})
</script>
</body>
```
#### 在 DOM 模板中使用組件註意事項
當使用 DOM 作為模版時(例如,將 `el` 選項掛載到一個已存在的元素上),
你會受到 HTML 的一些限制,
因為 Vue 只有在瀏覽器解析和標準化 HTML 後才能獲取模版內容。
尤其像這些元素 `<ul>` , `<ol>`, `<table>` , `<select>` 限制了能被它包裹的元素,
`<option>` 只能出現在其它元素內部。
在自定義組件中使用這些受限制的元素時會導致一些問題,例如:
```html
<table>
<my-row>...</my-row>
</table>
```
自定義組件 `<my-row>` 被認為是無效的內容,因此在渲染的時候會導致錯誤。
變通的方案是使用特殊的 `is` 屬性:
```html
<table>
<tr is="my-row"></tr>
</table>
```
**應當註意,如果您使用來自以下來源之一的字元串模板,這些限制將不適用:**
- `<script type="text/x-template">`
- JavaScript內聯模版字元串
- `.vue` 組件
因此,推薦使用字元串模板。
#### 組件的 `data` 必須是函數
在組件中,data 必須是函數,下麵是錯誤的方式:
```js
Vue.component('my-component', {
template: '<span>{{ message }}</span>',
data: {
message: 'hello'
}
})
```
正確的方式:
```js
data: function () {
return {
message: '組件的 data 必須是函數返回一個json字面量對象'
}
}
```
### 組件通信
- 使用 prop 傳遞數據
- props 命名規則
+ camelCase 和 kebab-case
- 動態 prop
+ v-bind
- 字面量語法 vs 動態語法
- 單向數據流
組件意味著協同工作,通常父子組件會是這樣的關係:組件 A 在它的模版中使用了組件 B 。
它們之間必然需要相互通信:
- 父組件要給子組件傳遞數據
- 子組件需要將它內部發生的事情告知給父組件
然而,在一個良好定義的介面中儘可能將父子組件解耦是很重要的。
這保證了每個組件可以在相對隔離的環境中書寫和理解,也大幅提高了組件的可維護性和可重用性。
在 Vue.js 中,父子組件的關係可以總結為 `props down, events up`:
- 父組件通過 `props` 向下傳遞數據給子組件
- 子組件通過 `events` 給父組件發送消息
![img/props-events.png](img/props-events.png)
#### 使用 prop 傳遞數據
組件實例的作用域是孤立的。
這意味著不能(也不應該)在子組件的模板內直接引用父組件的數據。
要讓子組件使用父組件的數據,我們需要通過子組件的props選項。
子組件要顯式地用 `props` 選項聲明它期待獲得的數據:
```js
Vue.component('child', {
// 聲明 props
props: ['message'],
// 就像 data 一樣,prop 可以用在模板內
// 同樣也可以在 vm 實例中像 “this.message” 這樣使用
template: '<span>{{ message }}</span>'
})
```
然後我們可以這樣向它傳入一個普通字元串:
```html
<child message="hello!"></child>
```
#### camelCase 和 kabab-case 命名規則
#### 動態 prop
#### 字面量語法 vs 動態語法
#### 單向數據流
prop 是單向綁定的:
- 當父組件的屬性發生變化時,將傳導給子組件
+ 子組件動態綁定的 prop,當父組件更新,子組件所有的 prop 都會得到更新
- 但是不會反過來
+ 也就是說,在子組件內部修改 prop 數據
* 子組件內部會響應更新
* 更新不會傳導給父組件
* 同時 Vue 會在控制台發出警告
* 對象和數組除外
* 如果 prop 是一個對象或數組,在子組件內部修改它會影響父組件的狀態
* 如果直接給 prop 中的對象或數組類型數據重新賦值,父組件也不會得到更新
+ 這是為了防止子組件無意間修改了父組件的狀態
+ 這會讓數據流的走向變得混亂而難以理解
為什麼我們會有修改 prop 中數據的衝動呢?
1. prop 作為初始值傳入後,子組件想要把它當作局部數據來用
2. prop 作為初始值傳入後,由子組件處理成其它數據輸出
對於這兩種原因,正確的方式是:
1. 定義一個局部變數,並用 prop 的值初始化它
```js
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
```
2. 定義一個計算屬性,處理 prop 的值並返回
```js
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
```
#### Prop 驗證
我們可以為組件的 props 指定驗證規格。
如果傳入的數據不符合規格,Vue 會發出警告。
當組件給其他人使用時,這就很有用了。
要指定驗證規格,需要使用對象的形式,而不能用字元串數組:
```js
Vue.component('example', {
props: {
// 基礎類型檢測 (`null` 意思是任何類型都可以)
propA: Number,
// 多種類型
propB: [String, Number],
// 必傳且是字元串
propC: {
type: String,
required: true
},
// 數字,有預設值
propD: {
type: Number,
default: 100
},
// 數組/對象的預設值應當由一個工廠函數返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定義驗證函數
propF: {
validator: function (value) {
return value > 10
}
}
}
})
```
`type` 可以是下麵原生的數據類型:
- String
- Number
- Boolean
- Function
- Object
- Array
`type` 也可以是一個自定義構造器函數(例如 Person),
Vue 會使用 `instanceof` 對數據進行檢測。
當 prop 驗證失敗,Vue會在拋出警告 (如果使用的是開發版本)。
### 自定義事件(父子通信)
- 使用 v-on 綁定自定義事件
- 使用自定義事件的表單輸入組件
- 非父子組件通信
父組件是使用 props 傳遞數據給子組件,
但如果子組件要把數據傳遞迴去,應該怎樣做?
那就是自定義事件!
#### 使用 v-on 綁定自定義事件
每個 Vue 實例都實現了事件介面:
- 使用 $on(eventName) 監聽事件
- 使用 $emit(eventName) 觸發事件
父組件可以在使用子組件的地方直接使用 `v-on` 監聽子組件發射的事件。
註意:不能使用 `$on` 偵聽子組件拋出的事件,而必須在模板里直接使用 `v-on` 綁定。
```html
<body>
<div id="app">
<p>{{ total }}</p>
<child v-on:increment="incrementTotal"></child>
<child v-on:increment="incrementTotal"></child>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
Vue.component('child', {
template: `
<div>
<span>{{ counter }}</span>
<button @click="increment">increment</button>
</div>`,
data () {
return {
counter: 0
}
},
methods: {
increment () {
this.counter += 1
this.$emit('increment')
}
}
})
new Vue({
el: '#app',
data: {
total: 0
},
methods: {
incrementTotal () {
this.total += 1
}
}
})
</script>
</body>
```
在本示例中,子組件已經和它外部完全解耦了。
它所做的只是報告自己的內部事件,至於父組件是否關心則與它無關。
有時候,可能想要在某個組件的根元素上綁定一個原生事件。
可以使用 `.native` 修飾 `v-on`。例如:
```html
<my-component v-on:click.native="doTheThing"></my-component>
```
#### 非父子組件通信
有時候兩個組件也需要通信(非父子關係)。
在簡單的場景下,可以使用一個空的 Vue 實例作為中央事件匯流排:
```html
<body>
<div id="app">
<child-a></child-a>
<child-b></child-b>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
const bus = new Vue()
Vue.component('child-a', {
template: `
<div>
<p>組件 child A</p>
<button @click="emitDataToB">發射數據到</button>
</div>
`,
methods: {
emitDataToB() {
// 在組件 A 中通過 $emit 發射 data 事件,組件 B 中的鉤子監聽了 data 事件
bus.$emit('data', '組件a傳遞過來的數據')
}
}
})
Vue.component('child-b', {
template: `
<div>
<p>組件 child B</p>
<p>{{ message }}</p>
</div>
`,
created() {
const vm = this
// 在組件 B 的鉤子中通過 bud 的 $on 監聽事件
bus.$on('data', function (data) {
vm.message = data
})
},
data() {
return {
message: 'hello child b'
}
}
})
new Vue({
el: '#app',
data: {},
})
</script>
</body>
```
### 使用 Slot 分發內容
- 編譯作用域
- 單個 Slot
- 具名 Slot
- 作用域插槽
在使用組件的時候,我們常常要像這樣組合它們:
```html
<app>
<app-header></app-header>
<app-footer></app-footer>
</app>
```
註意兩點:
1. `<app>` 組件不知道它的掛載點會有什麼內容。掛載點的內容是由 `<app>` 的父組件決定的
2. `<app>` 組件很可能有它自己的模板
為了讓組件可以組合,我們需要一種方式來混合父組件的內容和子組件自己的模板。
這個過程被稱為 **內容分發**(或 “transclusion”)。
Vue.js 實現了一個內容分發 API,參照了當前 Web 組件規範草案,
使用特殊的 `<slot>` 元素作為原始內容的插槽。
#### 編譯作用域
#### 單個 Slot
- 如果子組件沒有 `<slot>` 插口,否則父組件的內容會被丟棄
- 當子組件模板只有一個沒有屬性的 slot 時
+ 父組件整個內容片段都將插入到 slot 所在的 DOM 位置
+ 並替換掉 slot 標簽本身
+ 在 slot 標簽中的任何內容都被視為 備用內容
+ 備用內容在子組件的作用域內編譯
+ 並且只有宿主元素為空的時候,備用內容才會被編譯顯示出來
示例如下:
```html
<body>
<div id="app">
<bs-panel title="面板1">
面板1的內容
</bs-panel>
<bs-panel title="面板2">
面板2的內容
</bs-panel>
<bs-panel title="沒有分發內容的面板">
</bs-panel>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
Vue.component('bs-panel', {
template: `
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ title }}</h3>
</div>
<div class="panel-body">
<slot>
只有才沒有分發的內容時才會顯示
</slot>
</div>
</div>
`,
props: {
title: { type: String, required: true }
}
})
new Vue({
el: '#app',
data: {},
})
</script>
</body>
```
#### 具名 slot
- 在組合組件時,內容分發 API 是非常有用的機制
- `<slot>` 元素可以用一個特殊的屬性 `name` 來配置如何分發內容
- 多個 slot 可以有不同的名字。
- 具名 slot 將匹配內容片段中有對應 slot 特性的元素
- 仍然可以有一個匿名 slot,它是預設 slot
+ 作為找不到匹配的內容片段的備用插槽
+ 如果沒有預設的 slot,這些找不到插槽的內容片段將被拋棄
```html
<body>
<div id="app">
<app-layout>
<h1 slot="header">頂部</h1>
<p>內容段落</p>
<p>內容段落</p>
<p slot="footer">底部信息</p>
</app-layout>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
Vue.component('app-layout', {
template: `
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
})
new Vue({
el: '#app',
data: {},
})
</script>
</body>
```
#### 作用域插槽
- 目的:作用域插槽的目的就是可以將子組件內部的數據傳遞到外部
- 在子組件中,在 `slot` 標簽上通過屬性的方式將 prop 數據傳遞到外部
- 在父組件中,通過具有特殊屬性 `scope` 的 `<template>` 元素,表示它是作用域插槽的模板
+ `scope` 的值對應一個臨時變數名
+ 該變數接收來自子組件中通過 `slot` 元素屬性傳遞的 prop 數據
示例如下:
```html
<body>
<div id="app">
<child>
<template scope="props">
<p>hello from parent</p>
<p>{{ props.text }}</p>
<p>{{ props.message }}</p>
</template>
</child>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
Vue.component('child', {
template: `
<div class="child">
<input v-model="message" />
<slot text="hello from child" :message="message"></slot>
</div>
`,
data () {
return {
message: 'child message'
}
}
})
new Vue({
el: '#app',
data: {
},
})
</script>
</body>
```
作用域插槽更具代表性的用例是列表組件,允許組件自定義應該如何渲染列表每一項:
```html
<body>
<div id="app">
<my-awesome-list :todos="todos">
<template slot="item" scope="props">
<li class="my-fancy-item">{{ props.text }}</li>
</template>
</my-awesome-list>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
Vue.component('my-awesome-list', {
props: ['todos'],
template: `
<ul>
<slot name="item"
v-for="item in todos"
:text="item.title">
<!-- fallback content here -->
</slot>
</ul>
`
})
new Vue({
el: '#app',
data: {
todos: [
{ id: 1, title: '吃飯' },
{ id: 2, title: '睡覺' },
{ id: 3, title: '打豆豆' },
]
},
})
</script>
</body>
```
### 動態組件
通過保留的 `<component>` 元素,動態的綁定到它的 is 特性,
我們讓多個組件可以使用同一個掛載點:
簡單示例
```html
<body>
<div id="app">
<select v-model="currentView">
<option value="home">home</option>
<option value="posts">posts</option>
<option value="archive">archive</option>
</select>
<component v-bind:is="currentView"></component>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
currentView: 'home'
},
components: {
home: {
template: '<div>home</div>',
},
posts: {
template: '<div>posts</div>',
},
archive: {
template: '<div>archive</div>',
}
}
})
</script>
</body>
```
登陸註冊示例:
```html
<body>
<div id="app">
<ul>
<li><a href="JavaScript:void(0)" @click="defaultView = 'register'">註冊</a></li>
<li><a href="JavaScript:void(0)" @click="defaultView = 'login'">登陸</a></li>
</ul>
<div>
<component :is="defaultView"></component>
</div>
<hr><hr><hr><hr>
<div>
<!-- 可以使用 keep-alive 保持組件狀態 -->
<keep-alive>
<component :is="defaultView"></component>
</keep-alive>
</div>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
components: {
login: {
template: `
<form action="">
<div>
<label for="">用戶名</label>
<input type="text" />
</div>
<div>
<label for="">密碼</label>
<input type="password" />
</div>
<div>
<input type="submit" value="點擊登陸" />
</div>
</form>
`
},
register: {
template: `
<form action="">
<div>
<label for="">用戶名</label>
<input type="text" />
</div>
<div>
<label for="">密碼</label>
<input type="password" />
</div>
<div>
<label for="">確認密碼</label>
<input type="password" />
</div>
<div>
<label for="">驗證碼</label>
<input type="password" />
</div>
<div>
<input type="submit" value="點擊註冊" />
</div>
</form>
`
}
},
data: {
defaultView: 'login'
},
})
</script>
</body>
```
---
## 使用 Vue 的一些經驗
### 調試 Vue
- https://github.com/vuejs/vue-devtools
- https://github.com/MiCottOn/DejaVue
### 解決表達式閃爍
1. 將所有 `{{}}` 通過 `v-text` 替換
2. 使用 `v-cloak` 解決
第一,在要渲染處理的 DOM 節點上添加一個指令 `v-cloak`:
```html
<div id="app" ng-cloak>
{{ message }}
</div>
```
第二,在 style 中加入一個屬性選擇器樣式:
```css
[v-cloak] {
display: none;
}
```
第三,解析執行機制:
1. 當瀏覽器解析處理到添加了 `v-cloak` 屬性的節點的時候,屬性樣式被作用上來,也就是說預設這個容器就是隱藏著的
2. 然後當 Vue 程式解析渲染完HTML模板的時候,自動將容器上的 `v-cloak` 屬性移除