組件是vue最強(qiáng)大的功能之一散罕,組件可以擴(kuò)展 HTML 元素分歇,封裝可重用的代碼。在較高層面上欧漱,組件是自定義元素职抡, Vue.js 的編譯器為它添加特殊功能。在有些情況下误甚,組件也可以是原生 HTML 元素的形式缚甩,以 is 特性擴(kuò)展。
(一)注冊一個全局組件:(對于自定義標(biāo)簽名窑邦,Vue.js不要求強(qiáng)制遵循W3C規(guī)則(小寫擅威,并且包含一個短杠),盡管遵循這個規(guī)則比較好)
Vue.component('my-component',{
//內(nèi)容
})
組件在注冊之后冈钦,便可以在父實(shí)例的模塊中以自定義元素
<my-component></my-component>
的形式使用了郊丛,
重點(diǎn):必須要確保在初始化根實(shí)例之前注冊了組件
舉例:
<div id="app">
<my-component></my-component>
</div>
//全局注冊
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
//初始化根實(shí)例
new Vue({
el: "#app",
data: {
}
});
渲染結(jié)果:
<div id="app">
<div>A custom component!</div>
</div>
一般在項(xiàng)目中,不必在全局注冊每個組件瞧筛。通過使用組件實(shí)例選項(xiàng)注冊宾袜,可以使組件僅在另一個實(shí)例/組件的作用域中可用:
var Child = {
template: '<div>A custom component!</div>'
}
new Vue({
el: "#app",
data: {
},
components: {
'my-component': Child
}
});
(二)DOM模板解析說明:
當(dāng)使用 DOM 作為模版時(例如,將 el 選項(xiàng)掛載到一個已存在的元素上), 你會受到 HTML 的一些限制驾窟,因?yàn)?Vue 只有在瀏覽器解析和標(biāo)準(zhǔn)化 HTML 后才能獲取模版內(nèi)容庆猫。尤其像這些元素 <ul> ,<ol>绅络,<table> 月培,<select> 限制了能被它包裹的元素, 而一些像 <option> 這樣的元素只能出現(xiàn)在某些其它元素內(nèi)部恩急。
在自定義組件中使用這些受限制的元素時會導(dǎo)致一些問題杉畜,例如:
<table>
<my-row>...</my-row>
</table>
以上中,<mr-row>是會被認(rèn)為是無效內(nèi)容衷恭,因?yàn)閠able中只能包裹td,th,tr等元素此叠,變通方案是使用特殊的is屬性:
<table>
<tr is="my-row"></tr>
</table>
(三)data必須是函數(shù)
我們先來看一個案例:
<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
data: function () {
return data
}
})
new Vue({
el: '#example-2'
})
你會發(fā)現(xiàn),三個按鈕是聯(lián)動的K嬷椤C鹪!
因?yàn)樯厦嬷写翱矗琩ada是一個函數(shù)茸歧,我們返回給每個組件的實(shí)例引用了同一個data對象,由于這三個組件共享了同一個data显沈,因此增加一個counter會影響所有的組件
為了解決這個問題软瞎,我們需要將data設(shè)置為一個方法函數(shù)
data: function () {
return {
counter: 0
}
}
接下來逢唤,每個按鈕都會有了自己的狀態(tài)了
(四)構(gòu)成組件
在Vue中,父子組件的關(guān)系可以總結(jié)為props down,events up涤浇。父組件通過props向下傳遞數(shù)據(jù)給子組件鳖藕,子組件通過events給父組件發(fā)送消息
先來看看prop
組件實(shí)例的作用域是孤立的。這意味著不能(也不應(yīng)該)在子組件的模板內(nèi)直接引用父組件的數(shù)據(jù)只锭。要讓子組件使用父組件的數(shù)據(jù)吊奢,我們需要通過子組件的props選項(xiàng)。
還是舉例說明:
子組件要顯示的用props選項(xiàng)聲明它期待獲得的數(shù)據(jù):
Vue.component('my-component', {
//聲明props
props: ['message'],
//就像data一樣纹烹,prop可以用在模板內(nèi)
//同樣也可以在vm實(shí)例中像"this.message" 這樣使用
template: '<div>{{ message }}</div>'
})
在使用時可以這樣傳入一個普通字符串
<div id="app">
<my-component message="hello!"></my-component>
</div>
特別注意:
HTML 特性是不區(qū)分大小寫的页滚。所以,當(dāng)使用的不是字符串模版铺呵,camelCased (駝峰式) 命名的 prop 需要轉(zhuǎn)換為相對應(yīng)的 kebab-case (短橫線隔開式) 命名:
Vue.component('child', {
// camelCase in JavaScript
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})
<!-- kebab-case in HTML -->
<child my-message="hello!"></child>
動態(tài)prop
在模板中裹驰,要動態(tài)地綁定父組件的數(shù)據(jù)到子模板的props,與綁定到任何普通的HTML特性相類似片挂,就是用 v-bind幻林。每當(dāng)父組件的數(shù)據(jù)變化時,該變化也會傳導(dǎo)給子組件:
<div id="app">
<my-component :my-message="a"></my-component>
</div>
var Child = {
props: ['myMessage'],
template: '<div>{{ myMessage }}</div>'
}
new Vue({
el: "#app",
data: {
a:1
},
components: {
'my-component': Child
}
});
字面量語法VS動態(tài)語法
初學(xué)者常犯的一個錯誤就是使用字面量語法傳遞數(shù)值:
<!-- 傳遞了一個字符串 "1" -->
<comp some-prop="1"></comp>
因?yàn)樗且粋€字面 prop 音念,它的值是字符串 "1" 而不是number沪饺。如果想傳遞一個實(shí)際的number,需要使用 v-bind 闷愤,從而讓它的值被當(dāng)作 JavaScript 表達(dá)式計算:
<!-- 傳遞實(shí)際的 number -->
<comp v-bind:some-prop="1"></comp>
單項(xiàng)數(shù)據(jù)流
prop是單向綁定的:當(dāng)父組件的屬性變化時整葡,將傳導(dǎo)給子組件,但是不會反過來讥脐。這是為了防止子組件無意修改了父組件的狀態(tài)--這會讓應(yīng)用的數(shù)據(jù)流難以理解
另外遭居,每次父組件更新時,子組件的所有prop都會更新為最新值旬渠,這意味著你不應(yīng)該在子組件內(nèi)改變prop
對于為什么會有修改prop中數(shù)據(jù)的沖動俱萍,VUE官方給出的解釋:
1.prop 作為初始值傳入后,子組件想把它當(dāng)作局部數(shù)據(jù)來用告丢;
2.prop 作為初始值傳入枪蘑,由子組件處理成其它數(shù)據(jù)輸出。
對于這兩種原因岖免,正確的應(yīng)對方式時:
1.定義一個局部變量岳颇,并用prop的值初始化它:
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
2.定義一個計算屬性,處理 prop 的值并返回觅捆。
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意在 JavaScript 中對象和數(shù)組是引用類型赦役,指向同一個內(nèi)存空間麻敌,如果 prop 是一個對象或數(shù)組栅炒,在子組件內(nèi)部改變它會影響父組件的狀態(tài)。
prop驗(yàn)證
我們可以為組件的props指定驗(yàn)證規(guī)格,如果傳入的數(shù)據(jù)不符合規(guī)格赢赊,vue會發(fā)出警告乙漓,當(dāng)組件給其他人使用時,這很有用释移。
要指定驗(yàn)證規(guī)格叭披,需要用對象的形式,而不能用字符串?dāng)?shù)組
比如:
Vue.component('example',{
props: {
//基礎(chǔ)類型檢測(null意思是任何類型都可以)
propA:Number,
//多種類型
propB: [String,Number],
//必傳且是字符串
propC: {
type: String,
required: true
},
//數(shù)字玩讳,有默認(rèn)值
propD: {
type: Number,
default: 100
},
//數(shù)組/對象的默認(rèn)值應(yīng)當(dāng)由一個工廠函數(shù)返回
propE: {
type: Object,
default: function() {
return {
message: 'hello'
}
}
},
//自定義驗(yàn)證函數(shù)
propF: {
validator: function(value) {
return value > 10
}
}
}
})
type可以使下面原生構(gòu)造器
String, Number, Boolean, Function, Object, Array
type也可以是一個自定義構(gòu)造器函數(shù)涩蜘,使用instanceof檢測。
(四)自定義事件
父組件是使用props傳遞數(shù)據(jù)給子組件熏纯,但是如果子組件要把數(shù)據(jù)傳遞回去同诫,應(yīng)該怎么做呢?那就是接下來要說的是自定義事件
使用v-on綁定自定義事件
來看一個例子
使用 $on(eventName) 監(jiān)聽事件
使用 $emit(eventName) 觸發(fā)事件
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function(){
return {
counter: 0
}
},
methods: {
increment: function() {
this.counter += 1
this.$emit('increment')
}
}
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function() {
this.total += 1
}
}
})
在本例中樟澜,子組件已經(jīng)和它外部完全解耦了误窖。它所做的只是報告自己的內(nèi)部事件,至于父組件是否關(guān)心則與它無關(guān)秩贰。留意到這一點(diǎn)很重要霹俺。
使用自定義事件的表單輸入組件
自定義事件可以用來創(chuàng)建自定義的表單輸入組件,使用 v-model 來進(jìn)行數(shù)據(jù)雙向綁定
非父子組件通信
有時候兩個組件也需要通信(非父子關(guān)系)在簡單的場景下毒费,可以使用一個空的 Vue 實(shí)例作為中央事件總線:
var bus = new Vue()
// 觸發(fā)組件 A 中的事件
bus.$emit('id-selected', 1)
// 在組件 B 創(chuàng)建的鉤子中監(jiān)聽事件
bus.$on('id-selected', function (id) {
// ...
})
(五)使用Slot分發(fā)內(nèi)容
為了讓組件可以組合丙唧,我們需要一種方式來混合父組件的內(nèi)容與子組件自己的模板,這個過程被稱作內(nèi)容分發(fā)
(1)編譯作用域
父組件模板的內(nèi)容在父組件作用域內(nèi)編譯觅玻;子組件模板的內(nèi)容在子組件作用域內(nèi)編譯艇棕。
如果要綁定作用域內(nèi)的指令到一個組件的根節(jié)點(diǎn),你應(yīng)當(dāng)在組件自己的模板上做:
Vue.component('child-component', {
//有效串塑,因?yàn)槭窃谡_的作用域內(nèi)
template: '<div v-show="someChildProperty">Child</div>',
data: function() {
return {
someChildProperty: true
}
}
})
(2)單個Slot
--------未完待續(xù)