# vuejs tutorialcss
![vue-logo](img/logo.png)html
---vue
## 搭建案例演示自動刷新環境node
建立一個 `package.josn` 文件:react
```bash
npm init -y
```git
安裝 `browser-sync`:github
```bash
# npm install --save-dev browser-sync
# 將 browser-sync 包保存到開發依賴中
# 就能夠執行 npm install 的時候加入 --production 選項不安裝這個包
npm i -D browser-sync
```npm
在 package.json 文件中加入如下內容:json
```json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "browser-sync start --server --directory --files \"code/*.html\""
}
```api
打開終端,啓動開發預覽服務:
```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` 屬性移除