[toc]
## 使用組件
在 Vue.js 中,我們可以用 Vue 擴展出來的 ViewModel 子類當做可復用的組件。這在概念上與 Web Components 非常相似,不同之處在于 Vue 的組件無需任何 polyfill。要創建一個組件,只需調用 Vue.extend() 來生成一個 Vue 的子類構造函數:
~~~
// 擴展 Vue 得到一個可復用的構造函數
var MyComponent = Vue.extend({
template: '<p>A custom component!</p>'
})
~~~
Vue 的構造函數可接收的大部分選項都能在 Vue.extend() 中使用,不過也有兩個特例:data 和 el。由于每個 Vue 的實例都應該有自己的 $data 和 $el,我們顯然不希望傳遞給 Vue.extend() 的值被所有通過這個構造函數創建的實例所共享。因此如果要定義組件初始化默認數據和元素的方式,應該傳入一個函數:
~~~
var ComponentWithDefaultData = Vue.extend({
data: function () {
return {
title: 'Hello!'
}
}
})
~~~
接下來,就可以用 Vue.component() 來注冊這個構造函數了:
~~~
//把構造函數注冊到 my-component 這個 id
Vue.component('my-component', MyComponent)
~~~
為了更簡單,也可以直接傳入 option 對象來代替構造函數。如果接收到的是一個對象,Vue.component() 會為你隱式調用 Vue.extend():
~~~
// 注意:該方法返回全局 Vue 對象,
// 而非注冊的構造函數
Vue.component('my-component', {
template: '<p>A custom component!</p>'
})
~~~
之后就能在父級實例的模板中使用注冊過的組件了 (務必在初始化根實例之前注冊組件) :
~~~
<!-- 父級模板 -->
<my-component></my-component>
~~~
渲染結果:
~~~
<p>A custom component!</p>
~~~
你沒有必要,也不應該全局注冊所有組件。你可以限制一個組件僅對另一個組件及其后代可用,只要在另一個組件的 components 選項中傳入這個組件即可 (這種封裝形式同樣適用于其他資源,例如指令和過濾器) :
~~~
var Parent = Vue.extend({
components: {
child: {
// child 只能被
// Parent 及其后代組件使用
}
}
})
~~~
理解 Vue.extend() 和 Vue.component() 的區別非常重要。由于 Vue 本身是一個構造函數,Vue.extend() 是一個類繼承方法。它用來創建一個 Vue 的子類并返回其構造函數。而另一方面,Vue.component() 是一個類似 Vue.directive() 和 Vue.filter() 的資源注冊方法。它作用是建立指定的構造函數與 ID 字符串間的關系,從而讓 Vue.js 能在模板中使用它。直接向 Vue.component() 傳遞 options 時,它會在內部調用 Vue.extend()。
Vue.js 支持兩種不同風格的調用組件的 API:命令式的基于構造函數的 API,以及基于模板的聲明式的 Web Components 風格 API。如果你感到困惑,想一下通過 new Image() 和通過 <img> 標簽這兩種創建圖片元素的方式。它們都在各自的適用場景下發揮著作用,為了盡可能靈活,Vue.js 同時提供這兩種方式。
table 元素對能出現在其內部的元素類型有限制,因此自定義元素會被提到外部而且無法正常渲染。在那種情況下你可以使用指令式組件語法: `<tr v-component="my-component"></tr>`。
## 數據流
### 通過 prop 傳遞數據
默認情況下,組件有獨立作用域。這意味著你無法在子組件的模板中引用父級的數據。為了傳遞數據到擁有獨立作用域的子組件中,我們需要用到 prop。
一個 “prop” 是指組件的數據對象上的一個預期會從父級組件取得的字段。一個子組件需要通過 prop 選項顯式聲明它希望獲得的 prop:
~~~
Vue.component('child', {
// 聲明 prop
props: ['msg'],
// prop 可以在模板內部被使用,
// 也可以類似 `this.msg` 這樣來賦值
template: '<span>{{msg}}</span>'
})
~~~
然后,我們可以像這樣向這個組件傳遞數據:
~~~
<child msg="hello!"></child>
~~~
結果:
~~~
hello!
~~~
### 駝峰命名 vs. 連字符命名
HTML 特性是大小寫不敏感的。當駝峰式的 prop 名在 HTML 中作為特性名出現時,你需要用對應的連字符(短橫)分隔形式代替:
~~~
Vue.component('child', {
props: ['myMessage'],
template: '<span>{{myMessage}}</span>'
})
<!-- 重要:使用連字符分隔的名稱! -->
<child my-message="hello!"></child>
~~~
### 動態 prop
我們同樣能夠從父級向下傳遞動態數據。例如:
~~~
<div>
<input v-model="parentMsg">
<br>
<child msg="{{parentMsg}}"></child>
</div>
~~~
結果:

> 暴露 $data 作為 prop 也是可行的。傳入的值必須是一個對象,它會被用來替換組件默認的 $data 對象。
## 傳遞回調作為 prop
同樣可以向下傳遞一個方法或語句作為子組件的一個回調方法。借此可以進行聲明式的、解耦的父子通信:
~~~
Vue.component('parent', {
// ...
methods: {
onChildLoaded: function (msg) {
console.log(msg)
}
}
})
Vue.component('child', {
// ...
props: ['onLoad'],
ready: function () {
this.onLoad('message from child!')
}
})
<!-- 父級模板 -->
<child on-load="{{onChildLoaded}}"></child>
~~~
## prop 綁定類型
默認情況下,所有 prop 都會在子級和父級的屬性之間建立一個單向向下傳遞的綁定關系:當父級的屬性更新時,它將向下同步至子級,反之則不會。這種默認設定是為了防止子級組件意外篡改父級的狀態,那將導致難以推導應用的數據流。不過也可以顯式指定一個雙向或者一次性的綁定:
對比這些語法:
~~~
<!-- 默認情況下,單向綁定 -->
<child msg="{{parentMsg}}"></child>
`<!-- 顯式雙向綁定 -->
<child msg="{{@ parentMsg}}"></child>
`<!-- 顯示一次性綁定 -->
<child msg="{{* parentMsg}}"></child>
~~~
雙向綁定會反向同步子級的 msg 屬性到父級的 parentMsg 屬性。一次性綁定在完成之后不會在父級和子級之間同步未來發生的變化。
注意如果傳遞的 prop 值是對象或數組,將會是引用傳遞。在子級改動對象或數組將會影響到父級的狀態,這種情況會無視你使用的綁定的類型。
## prop 規則
組件可以對接收的 prop 聲明一定的規則限制。在開發給他人使用的組件時這會很有用,因為對 prop 的有效性檢驗可以看做是組件 API 的一部分,并且能保證用戶正確地使用了組件。與直接把 prop 定義成字符串不同,你需要使用包含驗證規則的對象:
~~~
Vue.component('example', {
props: {
// 基本類型檢查 (`null` 表示接受所有類型)
onSomeEvent: Function,
// 必需性檢查
requiredProp: {
type: String,
required: true
},
// 指定默認值
propWithDefault: {
type: Number,
default: 100
},
// 對象或數組類型的默認值
// 應該由工廠函數返回
propWithObjectDefault: {
type: Object,
default: function () {
return { msg: 'hello' }
}
},
// 雙向 prop。
// 如果綁定類型不匹配將拋出警告.
twoWayProp: {
twoWay: true
},
// 自定義驗證函數
greaterThanTen: {
validator: function (value) {
return value > 10
}
}
}
})
~~~
其中 type 可以是以下任一原生構造函數:
- String
- Number
- Boolean
- Function
- Object
- Array
另外,type 還可以是自定義構造函數,斷言將會是一個 instanceof 檢查。
如果 prop 檢驗不通過,Vue 會拒絕這次針對子組件的賦值,并且在使用開發版本時會拋出一個警告。
## 繼承父級作用域
如果有需要,你也可以使用 inherit: true 選項來讓子組件通過原型鏈繼承父級的全部屬性:
~~~
var parent = new Vue({
data: {
a: 1
}
})
// $addChild() 是一個實例方法,
// 它允許你用代碼創建子實例。
var child = parent.$addChild({
inherit: true,
data: {
b: 2
}
})
console.log(child.a) // -> 1
console.log(child.b) // -> 2
parent.a = 3
console.log(child.a) // -> 3
~~~
這里有一點需要注意:由于 Vue 實例上的數據屬性都是 getter/setter,設置 child.a = 2 會直接改變 parent.a 的值,而非在子級創建一個新屬性遮蔽父級中的屬性:
~~~
child.a = 4
console.log(parent.a) // -> 4
console.log(child.hasOwnProperty('a')) // -> false
~~~
## 作用域注意事項
當組件被用在父模板中時,例如:
~~~
<!-- 父模板 -->
<my-component v-show="active" v-on="click:onClick"></my-component>
~~~
這里的命令 (v-show 和 v-on) 會在父作用域編譯,所以 active 和 onClick 的取值取決于父級。任何子模版中的命令和插值都會在子作用域中編譯。這樣使得上下級組件間更好地分離。
閱讀 組件作用域 了解更多細節。
## 組件生命周期
每一個組件,或者說 Vue 的實例,都有著自己的生命周期:它會被創建、編譯、插入、移除,最終銷毀。在這每一個時間點,實例都會觸發相應的事件,而在創建實例或者定義組件時,我們可以傳入生命周期鉤子函數來響應這些事件。例如:
~~~
var MyComponent = Vue.extend({
created: function () {
console.log('An instance of MyComponent has been created!')
}
})
~~~
查閱 API 文檔中可用的 生命周期鉤子函數完整列表。
## 動態組件
你可以使用內置的 <component> 元素在組件間動態切換來實現 “頁面切換”:
~~~
new Vue({
el: 'body',
data: {
currentView: 'home'
},
components: {
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})
<component is="{{currentView}}">
<!-- 內容隨 vm.currentview 一同改變! -->
</component>
~~~
如果希望被切換出去的組件保持存活,從而保留它的當前狀態或者避免反復重新渲染,你可以加上 keep-alive 特性參數:
~~~
<component is="{{currentView}}" keep-alive>
<!-- 不活躍的的組件會被緩存! -->
</component>
~~~
## 過渡控制
有兩個額外的特性參數能夠支持對需要渲染或過渡的組件進行高級控制。
## wait-for 等待事件
等待即將進入的組件觸發該事件后再插入 DOM。這就允許你等待數據異步加載完成后再觸發過渡,避免顯示空白內容。
這一特性可以用于靜態和動態組件。注意:對于動態組件,所有有待渲染的組件都必須通過 $emit 觸發指定事件,否則他們永遠不會被插入。
示例:
~~~
<!-- 靜態組件 -->
<my-component wait-for="data-loaded"></my-component>
<!-- 動態組件 -->
<component is="{{view}}" wait-for="data-loaded"></component>
{ // 組件定義
// 獲取數據并在編譯完成鉤子函數中異步觸發事件。
// 這里jQuery只是用作演示。
compiled: function () {
var self = this $.ajax({
success: function (data) {
self.$data = data self.$emit('data-loaded')
}
})
}
}
~~~
## `transition-mode` 過渡模式
`transition-mode` 特性參數允許指定兩個動態組件之間的過渡應如何進行。
默認情況下,進入組件和退出組件的過渡是同時進行的。這個特性參數允許設置成另外兩種模式:
- `in-out`:先進后出;先執行新組件過渡,當前組件在新組件過渡結束后執行過渡并退出。
- `out-in`:先出后進;當前組件首先執行過渡并退出,新組件在當前組件過渡結束后執行過渡并進入。
**示例**:
~~~
<component is="{{view}}" v-transition="fade" transition-mode="out-in">
~~~
## 列表與組件
對于一個對象數組,你可以把組件和 `v-repeat` 組合使用。這種場景下,對于數組中的每個對象,都以該對象為 `$data` 創建一個子組件,以指定組件作為構造函數。
~~~
<ul id="list-example">
<user-profile v-repeat="users"></user-profile>
</ul>
new Vue({
el: '#list-example',
data: {
users: [
{ name: 'Chuck Norris', email: 'chuck@norris.com' },
{ name: 'Bruce Lee', email: 'bruce@lee.com' }
]
},
components: {
'user-profile': {
template: '{{name}} {{email}}'
}
}
})
~~~
**結果**:
~~~
- Chuck Norris - chuck@norris.com
- Bruce Lee - bruce@lee.com
~~~
## 在組件循環中使用標識符
在組件中標識符語法同樣適用,并且被循環的數據會被設置成組件的一個屬性,以標識符作為鍵名:
> 注意一旦組件跟 `v-repeat`一同使用,同樣的作用域規則也會被應用到該組件容器上的其他命令。結果就是,你在父級模板中將獲取不到 `$index`;只能在組件自身的模板中獲取。
> 或者,你也可以通過循環 `<template>` 來建立一個媒介作用域,但是大多數情況下在組件內部使用 `$index` 是更好的實踐。
## 子組件引用
某些情況下需要通過 JavaScript 訪問嵌套的子組件。要實現這種操作,需要使用 `v-ref` 為子組件分配一個 ID。例如:
~~~
var parent = new Vue({ el: '#parent' }) // 訪問子組件 var child = parent.$.profile
~~~
當 `v-ref` 與 `v-repeat` 一同使用時,會獲得一個與數據數組對應的子組件數組。
## 事件系統
雖然你可以直接訪問一個 Vue 實例的子級與父級,但是通過內建的事件系統進行跨組件通訊更為便捷。這還能使你的代碼進一步解耦,變得更易于維護。一旦建立了上下級關系,就能使用組件的 **事件實例方法** 來分發和觸發事件。
~~~
var parent = new Vue({
template: '',
created: function () {
this.$on('child-created', function (child) {
console.log('new child created: ')
console.log(child)
})
},
components: {
child: {
created: function () {
this.$dispatch('child-created', this)
}
}
}
}).$mount()
~~~
## 私有資源
有時一個組件需要使用類似命令、過濾器和子組件這樣的資源,但是又希望把這些資源封裝起來以便自己在別處復用。這一點可以用私有資源實例化選項來實現。私有資源只能被擁有該資源的組件及其繼承組件和子組件的實例訪問。
~~~
// 全部5種類型的資源
var MyComponent = Vue.extend({
directives: {
// “id : 定義”鍵值對,與處理全局方法的方式相同
'private-directive': function () {
// ...
}
},
filters: { // ... },
components: { // ... },
partials: { // ... },
effects: { // ... }
})
~~~
> 你可以通過設置 `Vue.config.strict = true` 阻止子組件訪問父組件的私有資源。
又或者,可以用與全局資源注冊方法類似的鏈式 API 為現有組件構造方法添加私有資源:
~~~
MyComponent .directive('...', {}) .filter('...', function () {}) .component('...', {}) // ...
~~~
## 資源命名約定
有些資源,諸如組件和指令,會以 HTML 特性或自定義 HTML 標簽的方式出現在模板中。因為 HTML 特性名和標簽名都是**大小寫不敏感**的,我們經常需要用連字符(短橫)連接命名取代駝峰命名。**從 0.12.9 開始**,我們支持將資源進行駝峰命名,同時在模板里用連字符命名法使用它們。
**示例**
~~~
// in a component definition components: {
// 用駝峰命名注冊組件 myComponent: { /... / }
}
~~~