1.組件(Component)是Vue.js最核心的功能废登。
2.組件的注冊有全局注冊和局部注冊兩種方式谷市。全局注冊后,任何Vue示例都可以使用狐树。
全局注冊示例:
Vue.component('my-component',{
//選項
})
my-component 就是注冊的組件自定義標(biāo)簽名稱,推薦使用小寫加減號分割的形式命名三娩。
要在富實例中使用這個組件庵芭,必須要在示例創(chuàng)建前注冊,之后就可以用<my-component></my-component>的形式來使用組件了雀监,示例:
<div id="app">
<my-component></my-component>
</div>
<script>
Vue.component('my-component',{
template:'<div>這里是組件的內(nèi)容</div>'
})
var app = new Vue({
el:'#app'
})
</script>
template的DOM結(jié)構(gòu)必須被一個元素飽含喳挑,如果直接寫內(nèi)容,不帶<div></div> 是無法渲染的滔悉。
在Vue實例中,使用components選項可以局部注冊組件单绑,注冊后的組件只有在該實例作用域下有效回官。組件中可以使用components選項來注冊組件,使組件可以嵌套搂橙。示例:
<div id="app">
<my-component></my-component>
</div>
<script>
var child = {
template:'<div>這里是組件的內(nèi)容</div>'
}
var app = new Vue({
el:'#app',
components:{
'my-component':child
}
})
</script>
3.Vue組件的模版在某些情況下會收到HTML的限制歉提,比如<table>內(nèi)規(guī)定只允許是<tr>、<td>区转、<th>等這些表格元素苔巨,所以在<table>內(nèi)直接使用組件是無效的。在這種情況下废离,可以使用 特殊的is屬性 來掛載組件侄泽,示例:
<div id="app">
<table>
<tbody is="my-component"></tbody>
</table>
</div>
<script>
Vue.component('my-component',{
template:'<div>這里是組件的內(nèi)容</div>'
})
var app = new Vue({
el:'#app'
})
</script>
tbody在渲染時,會被替換為組件的內(nèi)容蜻韭。常見的限制元素還有<ul>悼尾、<ol>、<select>肖方。
如果使用的是字符串模版闺魏,是不受限制的,比如.vue單文件用法等俯画。
4.除了template選項外析桥,組件中還可以像Vue實例哪樣使用其他的選項,比如data艰垂、computed泡仗、methods等,但是在使用data時材泄,和實例稍有區(qū)別沮焕,data必須是函數(shù),然后將數(shù)據(jù)return 出去拉宗,例如:
<div id="app">
<my-component></my-component>
</div>
<script>
Vue.component('my-component',{
template:'<div>{{message}}</div>',
data:function(){
return {
message:'組件內(nèi)容'
}
}
})
var app = new Vue({
el:'#app'
})
</script>
JavaScript對象是引用關(guān)系峦树,所以如果return出的對象引用了外部的一個對象辣辫,那這個對象就是共享的,任何一方修改都會同步魁巩。例如:
<div id="app">
<my-component></my-component>
<my-component></my-component>
<my-component></my-component>
</div>
<script>
var data = {
counter:0
}
Vue.component('my-component',{
template:'<button @click="counter++">{{counter}}</button>',
data:function(){
return data;
}
})
var app = new Vue({
el:'#app'
})
</script>
組件使用了3此急灭,但是點擊任意一個button,3個的數(shù)字都會加1谷遂,那是因為組件的data引用的是外部的對象葬馋,這肯定不是我們期望的效果,所以給組件返回一個新的data對象來獨立肾扰,示例:
<div id="app">
<my-component></my-component>
<my-component></my-component>
<my-component></my-component>
</div>
<script>
Vue.component('my-component',{
template:'<button @click="counter++">{{counter}}</button>',
data:function(){
return {
counter:0
};
}
})
var app = new Vue({
el:'#app'
})
</script>
這樣畴嘶,點擊3個按鈕就互補影響了,達(dá)到了復(fù)用的目的集晚。
5.組件不僅僅是要把模版的內(nèi)容進(jìn)行復(fù)用窗悯,更重要的是組件間要進(jìn)行通信。通常偷拔,父組件的模版中包含子組件蒋院,父組件要正向地向子組件傳遞數(shù)據(jù)或參數(shù),子組件接收后根據(jù)參數(shù)的不同來渲染不同的內(nèi)容或執(zhí)行操作莲绰。這個正向傳遞數(shù)據(jù)的過程就是通過props來實現(xiàn)的欺旧。
6.在組件中,使用選項props來聲明須要從父級接收的數(shù)據(jù)蛤签,props的值可以是兩種辞友,一種是字符串?dāng)?shù)組,一種是對象震肮。
7.我們構(gòu)造一個數(shù)組踏枣,接受一個來自父級的數(shù)據(jù)message,并把它在組件模版中渲染钙蒙,示例代碼:
<div id="app">
<my-component message="來自父組件的數(shù)據(jù)"></my-component>
</div>
<script>
Vue.component('my-component',{
props:['message'],
template:'<div>{{message}}</div>'
})
var app = new Vue({
el:'#app'
})
</script>
props中聲明的數(shù)據(jù)與組件data函數(shù)return的數(shù)據(jù)主要區(qū)別就是props的來自父級茵瀑,而data中的是組件自己的數(shù)據(jù),作用域是組件本身躬厌,這兩種數(shù)據(jù)都可以在模版template及計算屬性computed和方法methods中使用马昨。上例的數(shù)據(jù)message就是通過props從父級傳遞過來的,在組件的自定義標(biāo)簽上直接寫該props的名稱扛施,如果要傳遞多個數(shù)據(jù)鸿捧,在props數(shù)組中添加項即可。
8.由于HTML特性不區(qū)分大小寫疙渣,當(dāng)使用DOM模版時匙奴,駝峰命名(camelCase)的props名稱要轉(zhuǎn)為短橫分割命名(kebab-case),如:
<div id="app">
<my-component warning-text="提示信息"></my-component>
</div>
<script>
Vue.component('my-component',{
props:['warningText'],
template:'<div>{{warningText}}</div>'
})
var app = new Vue({
el:'#app'
})
</script>
9.有時候妄荔,傳遞的數(shù)據(jù)并不是直接寫死的泼菌,而是來自父級的動態(tài)數(shù)據(jù)谍肤,這時可以使用指令v-bind來動態(tài)綁定props的值,當(dāng)父組件的數(shù)據(jù)變化時哗伯,也會傳遞給子組件荒揣。示例代碼:
<div id="app">
<input type="text" v-model="parentMessage">
<my-component :message="parentMessage"></my-component>
</div>
<script>
Vue.component('my-component',{
props:['message'],
template:'<div>{{message}}</div>'
})
var app = new Vue({
el:'#app',
data:{
parentMessage:''
}
})
</script>
這里用v-model綁定了父級的數(shù)據(jù)parentMessage,當(dāng)通過輸入框任意輸入時焊刹,子組件接收到的props “message” 也會實時響應(yīng)系任,并更新組件模版。
注意虐块,如果要直接傳遞數(shù)字俩滥、布爾值、數(shù)組贺奠、對象举农,而且不使用v-bind,傳遞的僅僅是字符串敞嗡,嘗試下面的示例來對比:
<div id="app">
<my-component message="[1,2,3]"></my-component>
<my-component :message="[1,2,3]"></my-component>
</div>
<script>
Vue.component('my-component',{
props:['message'],
template:'<div>{{message.length}}</div>'
})
var app = new Vue({
el:'#app'
})
</script>
同一個組件使用了兩次,區(qū)別是第二次使用的是v-bind航背。渲染后的結(jié)果是喉悴,第一個是7,第二個是才是數(shù)組的長度3玖媚。
10.Vue2.x通過props傳遞數(shù)據(jù)是單向的箕肃。也就是父組件數(shù)據(jù)變化時會傳遞給子組件,但是反過來步行今魔。
11.業(yè)務(wù)中經(jīng)常會遇到兩種需要改變prop的情況勺像,一種是父組件傳遞初始值近來,子組件將它作為初始值保存起來错森,在自己的作用域下可以隨意使用和修改吟宦。這種情況可以在組件data內(nèi)再聲明一個數(shù)據(jù),引用父組件的prop涩维,示例代碼:
<div id="app">
<my-component :init-count="1"></my-component>
</div>
<script>
Vue.component('my-component',{
props:['initCount'],
template:'<div>{{count}}</div>',
data:function(){
return {
count:this.initCount
}
}
})
var app = new Vue({
el:'#app'
})
</script>
組件中聲明了數(shù)據(jù)count殃姓,它在組件初始化時會獲取來自父組件的initCount,之后就與之無關(guān)了瓦阐,只用維護count蜗侈,這樣就可以避免直接操作initCount。
另一種情況就是prop作為需要被轉(zhuǎn)變的原始值傳入睡蟋。這種情況用計算屬性就可以了踏幻,例如:
<div id="app">
<my-component :width="100"></my-component>
</div>
<script>
Vue.component('my-component',{
props:['width'],
template:'<div :style="style">組件內(nèi)容</div>',
computed:{
style:function(){
return {
width: this.width + 'px',
border: '1px solid black'
}
}
}
})
var app = new Vue({
el:'#app'
})
</script>
因為CSS傳遞寬度要帶單位(px),但是每次都寫太麻煩戳杀,而且數(shù)值計算一般是不帶單位的该面,所以統(tǒng)一在組件內(nèi)使用計算屬性就可以了夭苗。
注意,在JavaScript中對象和數(shù)組是引用類型吆倦,指向同一個內(nèi)存空間听诸,所以props是對象和數(shù)組時,在子組件內(nèi)改變是會影響父組件的蚕泽。
12.上面介紹的props選項的值都是一個數(shù)組晌梨,除了數(shù)組外,還可以是對象须妻,當(dāng)prop需要驗證時仔蝌,就需要對象寫法。
一般當(dāng)你的組件需要提供給別人使用時荒吏,推薦都進(jìn)行數(shù)據(jù)驗證敛惊,比如某個數(shù)據(jù)必須是數(shù)字類型,如果傳入字符串绰更,就會在控制臺彈出警告瞧挤,以下是幾個prop的示例:
Vue.component('my-component',{
props:{
//必須是數(shù)字類型
propA:Number,
//必須是字符串或數(shù)字類型
propB:[String,Number],
//布爾值,如果沒有定義儡湾,默認(rèn)值就是true
propC:{
type:Boolean,
default:true
},
//數(shù)字特恬,而且是必傳
propD:{
type:Number,
required:true
},
//如果是數(shù)組或?qū)ο螅J(rèn)值必須是一個函數(shù)來返回
propE:{
type:Array,
default:function(){
return [];
}
},
//自定義一個驗證函數(shù)
propF:{
validator:function(value){
return value > 10;
}
}
}
驗證的type類型可以是:
- String
- Number
- Boolean
- Object
- Array
- Function
type也可以是一個自定義構(gòu)造器徐钠,使用instanceof檢測癌刽。
當(dāng)prop驗證失敗時,在開發(fā)版本下回在控制臺拋出一條警告尝丐。
13.Vue組件的通信場景:父子組件通信显拜、兄弟組件通信、跨級組件通信爹袁。
14.當(dāng)子組件需要向父組件傳遞數(shù)據(jù)時远荠,就要用到自定義事件。v-on除了監(jiān)聽DOM事件外失息,還可以用于組件之間的自定義事件矮台。
15.與JavaScript的設(shè)計模式——觀察者模式中的dispatchEvent和addEventListener這兩個方法類似。Vue組件也有一套模式根时,子組件用$emit()來觸發(fā)時間瘦赫,父組件用$on()來監(jiān)聽子組件的事件。
父組件也可以直接在子組件的自定義標(biāo)簽上使用v-on來監(jiān)聽子組件觸發(fā)的自定義事件蛤迎,示例代碼:
<div id="app">
<p>總數(shù):{{total}}</p>
<my-component
@increase="handleGetTotal"
@reduce="handleGetTotal"></my-component>
</div>
<script>
Vue.component('my-component',{
template:'\
<div>\
<button @click="handleIncrease">+1</button>\
<button @click="handleReduce">-1</button>\
</div>',
data:function(){
return {
counter:0
}
},
methods:{
handleIncrease:function(){
this.counter ++;
this.$emit('increase',this.counter);
},
handleReduce:function(){
this.counter --;
this.$emit('reduce',this.counter);
}
}
})
var app = new Vue({
el:'#app',
data:{
total:0
},
methods:{
handleGetTotal:function(total){
this.total = total;
}
}
})
</script>
上面的例子中确虱,子組件有兩個按鈕,分別實現(xiàn)加1和減1的效果替裆,在改變組件的data "counter"后校辩,通過$emit()再把它傳給父組件窘问,父組件用v-on:increase 和 v-on:reduce (示例使用的是語法糖)。$emit方法的第一個參數(shù)是自定義事件的名稱宜咒,例如示例的increase和reduce后面的參數(shù)都是要傳遞的數(shù)據(jù)惠赫,可以不填或填寫多個。
16.除了用 v-on 在組件上監(jiān)聽自定義事件外故黑,也可以監(jiān)聽DOM事件儿咱,這時可以用.native 修飾符表示監(jiān)聽的是一個原生事件,監(jiān)聽的是該組件的根元素场晶,示例如下:
<my-component v-on:click.native="handleClick"></my-component>
17.使用 v-model
Vue2.x可以在自定義組件上使用 v-model指令混埠,示例:
<div id="app">
<p>總數(shù):{{total}}</p>
<my-component v-model="total"></my-component>
</div>
<script>
Vue.component('my-component',{
template:'<button @click="handleClick">+1</button>',
data:function(){
return {
counter:0
}
},
methods:{
handleClick:function(){
this.counter ++;
this.$emit('input',this.counter);
}
}
})
var app = new Vue({
el:'#app',
data:{
total:0
}
})
</script>
仍然是點擊按鈕加1的效果,不過這次組件$emit()的事件名是特殊的input诗轻,在使用組件的父級钳宪,并沒有在<my-component>上使用@input="handler",而是直接用了v-model綁定的一個數(shù)據(jù)total扳炬。這也可以稱作是一個語法糖吏颖,因為上面的示例可以間接地用自定義事件來實現(xiàn):
<div id="app">
<p>總數(shù):{{total}}</p>
<my-component @input="handleGetTotal"></my-component>
</div>
<script>
Vue.component('my-component',{
template:'<button @click="handleClick">+1</button>',
data:function(){
return {
counter:0
}
},
methods:{
handleClick:function(){
this.counter ++;
this.$emit('input',this.counter);
}
}
})
var app = new Vue({
el:'#app',
data:{
total:0
},
methods:{
handleGetTotal:function(total){
this.total = total;
}
}
})
</script>
v-model還可以用來創(chuàng)建自定義的表單輸入組件,進(jìn)行數(shù)據(jù)雙向綁定恨樟,例如:
<div id="app">
<p>總數(shù):{{total}}</p>
<my-component v-model="total"></my-component>
<button @click="handleReduce">-1</button>
</div>
<script>
Vue.component('my-component',{
props:['value'],
template:'<input :value="value" @input="updateValue">',
data:function(){
return {
counter:0
}
},
methods:{
updateValue:function(event){
this.$emit('input',event.target.value);
}
}
})
var app = new Vue({
el:'#app',
data:{
total:0
},
methods:{
handleReduce:function(){
this.total --;
}
}
})
</script>
實現(xiàn)這樣一個具有雙向綁定的v-model組件要滿足下面兩個要求:
- 接收一個value屬性
- 在有新的value時觸發(fā)input事件
18.在Vue.js 2.x中半醉,推薦使用一個空的Vue實例作為中央事件總線(bus),也就是一個中介厌杜。示例代碼:
<div id="app">
{{ message }}
<component-a></component-a>
</div>
<script>
var bus = new Vue();
Vue.component('component-a',{
template:'<button @click="handleEvent">傳遞事件</button>',
methods:{
handleEvent:function(){
bus.$emit('on-message','來自組件component-a的內(nèi)容');
}
}
})
var app = new Vue({
el:'#app',
data:{
message:''
},
mounted:function(){
var _this = this;
//在實例初始化時,監(jiān)聽來自bus實例的事件
bus.$on('on-message',function(msg){
_this.message = msg;
})
}
})
</script>
首先創(chuàng)建了一個名為bus的空Vue實例计螺。然后全局定義了組件component-a夯尽;最后創(chuàng)建Vue實例app,在app初始化時登馒,也就是在生命周期mounted鉤子函數(shù)里監(jiān)聽了來自bus的事件on-message匙握,而在組件component-a中,點擊按鈕會通過bus把事件on-message發(fā)出去陈轿,此時app就會接收到來自bus的事件圈纺,進(jìn)而在回調(diào)里完成自己的業(yè)務(wù)邏輯。
這種方法巧妙而輕量地實現(xiàn)了任何組件間的通信麦射,包括父子蛾娶、兄弟、跨級潜秋。如果深入使用蛔琅,可以擴展bus實例,給它添加data峻呛、methods罗售、computed等選項辜窑,這些都是可以公用的,在業(yè)務(wù)中寨躁,尤其是協(xié)同開發(fā)時非常有用穆碎,因為經(jīng)常需要共享一些通用的信息,比如用戶登錄的昵稱职恳、性別所禀、郵箱等,還有用戶的授權(quán)token等话肖。只需在初始化時讓bus獲取一次北秽,任何時間,任何組件就可以從中直接使用了最筒,在單頁面富應(yīng)用(SPA)中會很實用贺氓。項目比較大時,可以選擇更好的狀態(tài)慣例解決方案vuex床蜘。
19.除了中央事件總線bus外辙培,還有兩種方法可以實現(xiàn)組件間通信:父鏈 和子組件索引。
20.在子組件中邢锯,使用this.$parent 可以直接訪問該組件的父實例或組件扬蕊,父組件也可以通過this.$children 訪問它的所有的子組件,而且可以遞歸向上或向下無限訪問丹擎,直到根實例或最內(nèi)層的組件尾抑。示例:
<div id="app">
{{ message }}
<component-a></component-a>
</div>
<script>
var bus = new Vue();
Vue.component('component-a',{
template:'<button @click="handleEvent">通過父鏈直接修改數(shù)據(jù)</button>',
methods:{
handleEvent:function(){
//訪問到父鏈后,可以做任何操作蒂培,比如直接修改數(shù)據(jù)
this.$parent.message = '來自組件component-a的內(nèi)容';
}
}
})
var app = new Vue({
el:'#app',
data:{
message:''
}
})
</script>
盡管Vue允許這樣操作再愈,但在業(yè)務(wù)中,子組件應(yīng)該盡可能地避免依賴父組件的數(shù)據(jù)护戳,更不應(yīng)該去主動修改它的數(shù)據(jù)翎冲,因為這樣使得父組件緊耦合,只看父組件媳荒,很難理解父組件的狀態(tài)抗悍,因為它可能被任意組件修改,理想情況下钳枕,只有組件自己能修改它的狀態(tài)缴渊。父子組件最好還是通過props和$emit來通信。
21.當(dāng)子組件較多時鱼炒,通過this.$children 來一一遍歷出我們需要的一個組件實例是比較困難的疟暖,尤其是組件動態(tài)渲染時,它們的序列是不固定的。
Vue提供了子組件索引的方法俐巴,用特殊的屬性ref來為子組件指定一個索引名稱骨望,示例:
<div id="app">
<button @click="handleRef">通過ref獲取子組件實例</button>
<component-a ref="comA"></component-a>
</div>
<script>
Vue.component('component-a',{
template:'<div>子組件</div>',
data:function(){
return {
message:'子組件內(nèi)容'
}
}
})
var app = new Vue({
el:'#app',
methods:{
handleRef:function(){
//通過$refs來訪問指定的實例
var msg = this.$refs.comA.message;
console.log(msg);
}
}
})
</script>
在父組件模版中,子組件標(biāo)簽上使用ref指定一個名稱欣舵,并在父組件內(nèi)通過this.$refs 來訪問指定名稱的子組件擎鸠。$refs只在組件渲染完成后才填充,并且它是非響應(yīng)式的缘圈。它僅僅作為一個直接訪問子組件的應(yīng)急方案劣光,應(yīng)當(dāng)避免在模板或計算屬性中使用$refs。
22.當(dāng)需要讓組件組合使用糟把,混合父組件的內(nèi)容和子組件的模板時绢涡,就會用到slot,這個過程叫作內(nèi)容分發(fā)(transclusion)遣疯。以<app>為例雄可,它有兩個特點:
- <app>組件不知道它的掛載點會有什么內(nèi)容。掛載點是由<app>的父組件決定的缠犀。
- <app>組件很可能有它自己的模板数苫。
props傳遞數(shù)據(jù)、events觸發(fā)事件和slot內(nèi)容分發(fā)就構(gòu)成了Vue組件的3個API來源辨液,再復(fù)雜的組件也是由這3部分構(gòu)成的虐急。
-
slot。如下圖滔迈,一個比較常規(guī)的網(wǎng)站布局:
image.png
這個網(wǎng)站由一級導(dǎo)航止吁、二級導(dǎo)航、左側(cè)列表燎悍、正文以及底部版權(quán)信息5個模塊組成敬惦,如果要將它們都組件化,這個結(jié)構(gòu)可能會是:
<app>
<menu-main></menu-main>
<menu-sub></menu-sub>
<div class="container">
<menu-left></menu-left>
<container></container>
</div>
</app>
當(dāng)需要讓組件組合使用间涵,混合父組件的內(nèi)容與子組件的模板時仁热,就會用到slot榜揖,這個過程叫做內(nèi)容分發(fā)(transclusion)勾哩。
以 <app>為例,它有兩個特點:
- <app>組件不知道它的掛載點會有什么內(nèi)容举哟。掛載點的內(nèi)容是由<app>的父組件決定的思劳。
- <app>組件很可能有它自己的模板
props傳遞數(shù)據(jù)、events觸發(fā)事件 和 slot 內(nèi)容分發(fā)就構(gòu)成了Vue組件的3個API來源妨猩,再復(fù)雜的組件也是由這3部分構(gòu)成的潜叛。
編譯的作用域。比如父組件有如下模板:
<child-component>
{{ message }}
</child-component>
這里的message就是一個slot,但是它綁定的是父組件的數(shù)據(jù)威兜,而不是組件<child-component>的數(shù)據(jù)销斟。
父組件模板的內(nèi)容是在父組件作用域內(nèi)編譯,子組件模板的內(nèi)容是在子組件作用域內(nèi)編譯椒舵。例如下面的示例:
<div id="app">
<child-component v-show="showChild"></child-component>
</div>
<script>
Vue.component('child-component',{
template:'<div>子組件</div>'
});
var app = new Vue({
el:'#app',
data:{
showChild:true
}
})
</script>
這里的狀態(tài)showChild綁定的是父組件的數(shù)據(jù)蚂踊,如果想在子組件上綁定,那應(yīng)該是:
<div id="app">
<child-component></child-component>
</div>
<script>
Vue.component('child-component',{
template:'<div v-show="showChild">子組件</div>',
data:function(){
return {
showChild:true
}
}
});
var app = new Vue({
el:'#app'
})
</script>
因此笔宿,slot分發(fā)的內(nèi)容犁钟,作用域是在父組件上的。
單個slot泼橘。在子組件內(nèi)使用特殊的<slot>元素就可以為這個子組件開啟一個slot(插槽)涝动,在父組件模板里,插入在子組件標(biāo)簽內(nèi)的所有內(nèi)容將替代子組件的<slot>標(biāo)簽及它的內(nèi)容炬灭。示例代碼:
<div id="app">
<child-component>
<p>分發(fā)的內(nèi)容</p>
<p>更多分發(fā)的內(nèi)容</p>
</child-component>
</div>
<script>
Vue.component('child-component',{
template:'\
<div>\
<slot>\
<p>如果父組件沒有插入內(nèi)容醋粟,我將作為默認(rèn)出現(xiàn)</p>\
</solt>\
</div>'
});
var app = new Vue({
el:'#app'
})
</script>
子組件child-component的模板內(nèi)定義了一個<slot>元素,并且用一個<p>作為默認(rèn)的內(nèi)容担败,在父組件沒有使用slot時昔穴,會渲染這段默認(rèn)的文本;如果寫入了slot提前,那就會替換整個<slot>吗货。所以上例渲染后的結(jié)果為:
<div id="app">
<div>
<p>分發(fā)的內(nèi)容</p>
<p>更多分發(fā)的內(nèi)容</p>
</div>
</div>
注意,子組件<slot>內(nèi)的備用內(nèi)容狈网,它的作用域是子組件本身宙搬。
具名slot。給<slot>元素指定一個name后可以分發(fā)多個內(nèi)容拓哺,具名slot可以與單個slot共存勇垛,例如:
<div id="app">
<child-component>
<h2 slot="header">標(biāo)題</h2>
<p>正文內(nèi)容</p>
<p>更多的正文內(nèi)容</p>
<div slot="footer">底部信息</div>
</child-component>
</div>
<script>
Vue.component('child-component',{
template:'\
<div class="container">\
<div class="header">\
<slot name="header"></slot>\
</div>\
<div class="main">\
<slot></slot>\
</div>\
<div class="footer">\
<slot name="footer"></slot>\
</div>\
</div>'
});
var app = new Vue({
el:'#app'
})
</script>
子組件內(nèi)聲明了3個<slot>元素,其中在<div class="main">內(nèi)的<slot>沒有使用name特性士鸥,它將作為默認(rèn)slot出現(xiàn)闲孤,父組件沒有使用slot特性的元素與內(nèi)容都將出現(xiàn)在這里。如果沒有指定默認(rèn)的匿名slot烤礁,父組件內(nèi)多余的內(nèi)容片段都將被拋棄讼积。上例最終渲染結(jié)果為:
<div id="app">
<div class="container">
<div class="header">
<h2>標(biāo)題</h2>
</div>
<div class="main">
<p>正文內(nèi)容</p>
<p>更多的正文內(nèi)容</p>
</div>
<div class="footer">
<div>底部信息</div>
</div>
</div>
</div>
在組合使用組件時,內(nèi)容分發(fā)API至關(guān)重要脚仔。
作用域插槽勤众。是一種特殊的slot据沈。使用一個可以復(fù)用的模板替換已渲染元素匀借。概念比較難理解窒篱,簡單示例:
<div id="app">
<child-component>
<template scope="props">
<p>來自父組件的內(nèi)容</p>
<p>{{props.msg}}</p>
</template>
</child-component>
</div>
<script>
Vue.component('child-component',{
template:'\
<div class="container">\
<slot msg="來自子組件的內(nèi)容"></slot>\
</div>'
});
var app = new Vue({
el:'#app'
})
</script>
觀察子組件的模板,在<slot>元素上有一個類似props傳遞數(shù)據(jù)給組件的寫法 msg = "xxx"拂蝎,將數(shù)據(jù)傳到了插槽账阻。父組件中使用了<template>元素火焰,而且擁有一個scope="props" 的特性今布,這里的props只是一個臨時變量,就像v-for="item in items"里面的item 一樣阻问。template 內(nèi)可以通過臨時變量props訪問來自子組件插槽的數(shù)據(jù)msg茅坛。
<div id="app">
<div class="container">
<p>來自父組件的內(nèi)容</p>
<p>來自子組件的內(nèi)容</p>
</div>
</div>
作用域插槽更具代表性的用例是列表組件,允許組件自定義應(yīng)該如何渲染列表每一項则拷。示例代碼:
<div id="app">
<my-list :books="books">
<!--作用域插槽也可以是具名的slot-->
<template slot="book" scope="props">
<li>{{ props.bookName }}</li>
</template>
</my-list>
</div>
<script>
Vue.component('my-list',{
props:{
books:{
type:Array,
default:function(){
return [];
}
}
},
template:'\
<ul>\
<slot name="book"\
v-for="book in books"\
:book-name="book.name">\
<!--這里也可以寫默認(rèn)slot內(nèi)容-->\
</slot>\
</ul>\
'
});
var app = new Vue({
el:'#app',
data:{
books:[
{ name:'《Vue.js實戰(zhàn)》'},
{ name:'《JavaScript 語言精粹》'},
{ name:'《JavaScript 高級程序設(shè)計》'}
]
}
})
</script>
子組件my-list接收一個來自父級的prop數(shù)組books贡蓖,并且將它在name為book的slot上使用v-for指令循環(huán),同時暴露一個變量bookName煌茬。
作用域插槽的使用場景就是既可以復(fù)用子組件的slot斥铺,又可以使slot內(nèi)容不一致。如果上例還在其他組件內(nèi)使用坛善,<li>的內(nèi)容渲染權(quán)是由使用者掌握的晾蜘,而數(shù)據(jù)卻可以通過臨時變量(比如props)從子組件內(nèi)獲取。
訪問slot眠屎。Vue.js 2.x提供了用來訪問被slot分發(fā)的內(nèi)容的方法 $slots剔交,示例:
<div id="app">
<child-component>
<h2 slot="header">標(biāo)題</h2>
<p>正文內(nèi)容</p>
<p>更多的正文內(nèi)容</p>
<div slot="footer">底部信息</div>
</child-component>
</div>
<script>
Vue.component('child-component',{
template:'\
<div class="container">\
<div class="header">\
<slot name="header"></slot>\
</div>\
<div class="main">\
<slot></slot>\
</div>\
<div class="footer">\
<slot name="footer"></slot>\
</div>\
</div>',
mounted:function(){
var header = this.$slots.header;
var main = this.$slots.default;
var footer = this.$slots.footer;
console.log(footer)
console.log(footer[0].elm.innerHTML)
}
});
var app = new Vue({
el:'#app'
})
</script>
通過slots.default包括了所有沒有被包含在具名slot中的節(jié)點改衩。$slots在業(yè)務(wù)中幾乎用不到岖常,在用render函數(shù)創(chuàng)建組件時會比較有用,但主要還是用于獨立組件開發(fā)中葫督。
24.遞歸組件竭鞍。組件在它的模板內(nèi)可以遞歸地調(diào)用自己,只要給組件設(shè)置name的選項就可以了橄镜。示例代碼:
<div id="app">
<child-component :count="1"></child-component>
</div>
<script>
Vue.component('child-component',{
name:'child-component',
props:{
count:{
type:Number,
default:1
}
},
template:'\
<div class="child">\
<child-component\
:count="count + 1"\
v-if="count < 3"></child-component>\
</div>'
})
var app = new Vue({
el:'#app'
})
</script>
設(shè)置name后偎快,在組件模板內(nèi)就可以遞歸使用了,不過需要注意洽胶,必須給一個條件來限制遞歸數(shù)量晒夹,否則會拋出錯誤:max stack size exceeded。
組件遞歸使用可以用來開發(fā)一些具有未知層級關(guān)系的獨立組件姊氓,比如級聯(lián)選擇器和樹形控件等丐怯。
25.內(nèi)聯(lián)模板。組件的模板一般都是在template選項內(nèi)定義的他膳。Vue提供了一個內(nèi)聯(lián)模板的功能响逢,在使用組件時绒窑,給組件標(biāo)簽使用inline-template特性棕孙,組件就會把它的內(nèi)容當(dāng)作模板,而不是把它當(dāng)內(nèi)容分發(fā),這讓模板更靈活蟀俊。示例:
<div id="app">
<child-component inline-template>
<div>
<h2>在父組件中定義子組件的模板</h2>
<p>{{message}}</p>
<p>{{msg}}</p>
</div>
</child-component>
</div>
<script>
Vue.component('child-component',{
data:function(){
return {
msg:'在子組件聲明的數(shù)據(jù)'
}
}
});
var app = new Vue({
el:'#app',
data:{
message:'在父組件聲明的數(shù)據(jù)'
}
})
</script>
渲染后的結(jié)果為:
<div id="app">
<div>
<h2>在父組件中定義子組件的模板</h2>
<p>在父組件聲明的數(shù)據(jù)</p>
<p>在子組件聲明的數(shù)據(jù)</p>
</div>
</div>
在父組件中聲明的數(shù)據(jù)message和子組件中聲明的數(shù)據(jù)msg钦铺,兩個都可以渲染(如果同名,優(yōu)先使用子組件的數(shù)據(jù))肢预。這反而是內(nèi)聯(lián)模板的缺點矛洞,就是作用域比較難理解,如果不是非常特殊的場景烫映,建議不要輕易使用內(nèi)聯(lián)模板沼本。
26.動態(tài)組件。Vue.js提供了一個特殊元素<component>用來動態(tài)地掛在不同的組件锭沟,使用is特性來選擇要掛載的組件抽兆。示例:
<div id="app">
<component :is="currentView"></component>
<button @click="handleChangeView('A')">切換到A</button>
<button @click="handleChangeView('B')">切換到B</button>
<button @click="handleChangeView('C')">切換到C</button>
</div>
<script>
var app = new Vue({
el:'#app',
components:{
comA:{
template:'<div>組件A</div>'
},
comB:{
template:'<div>組件B</div>'
},
comC:{
template:'<div>組件C</div>'
}
},
data:{
currentView:'comA'
},
methods:{
handleChangeView:function(component){
this.currentView = 'com' + component;
}
}
})
</script>
動態(tài)地改變currentView的值就可以動態(tài)掛載組件了。也可以直接綁定在組件對象上:
<div id="app">
<component :is="currentView"></component>
</div>
<script>
var Home = {
template:'<p>Welcome home ! </p>'
}
var app = new Vue({
el:'#app',
data:{
currentView: Home
}
})
</script>
27.異步組件族淮。當(dāng)你的工程足夠大辫红,使用的組件足夠多時,是時候考慮下性能問題了祝辣,因為一開始把所有的組件都加載是沒有必要的一筆開銷贴妻。好在Vue.js允許將組件定義為一個工廠函數(shù),動態(tài)地解析組件蝙斜。Vue.js只在組件需要渲染時觸發(fā)工廠函數(shù)名惩,并且把結(jié)果緩存起來,用于后面的再次渲染孕荠。例如:
<div id="app">
<child-component></child-component>
</div>
<script>
Vue.component('child-component',function(resolve,reject){
window.setTimeout(function(){
resolve({
template:'<div>我是異步渲染的</div>'
});
},2000);
});
var app = new Vue({
el:'#app'
})
</script>
工廠函數(shù)接收一個resolve回調(diào)绢片,在收到從服務(wù)器下載的組件定義時調(diào)用。也可以調(diào)用reject(reason)指示加載失敗岛琼。這里setTimeout 指示為了演示異步底循,具體的下載邏輯可以自己決定,比如把組件配置攜程一個對象配置槐瑞,通過Ajax來請求熙涤,然后調(diào)用resolve傳入配置選項。
28.$nextTick困檩。場景描述:有一個div祠挫,默認(rèn)用v-if將它隱藏,點擊一個按鈕后悼沿,改變v-if的值等舔,讓它顯示出來,同時拿到這個div的文本內(nèi)容糟趾。如果v-if的值是false慌植,直接去獲取div的內(nèi)容是獲取不到的甚牲,因為此時div還沒有被創(chuàng)建出來,那么應(yīng)該在點擊按鈕后蝶柿,改變v-if的值為true丈钙,div才會被創(chuàng)建,此時再去獲取交汤,示例代碼如下:
<div id="app">
<div id="div" v-if="showDiv">這是一段文本</div>
<button @click="getText">獲取div內(nèi)容</button>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
showDiv:false
},
methods:{
getText:function(){
this.showDiv = true;
var text = document.getElementById('div').innerHTML;
console.log(text)
}
}
})
</script>
這段代碼并不難理解雏赦,但是運行后在控制臺會拋出一個錯誤:cannot read property 'innerHTML' of null, 意思是獲取不到div元素。這就涉及Vue一個重要概念:異步更新隊列芙扎。
Vue在觀察到數(shù)據(jù)變化時并不是直接更新DOM星岗,而是開啟一個隊列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變戒洼。在緩沖時會去除重復(fù)數(shù)據(jù)伍茄,從而避免不必要的計算和DOM操作。然后施逾,在下一個事件循環(huán)tick中敷矫,Vue刷新隊列并執(zhí)行實際(已去重的)工作。所以如果你用一個for循環(huán)來動態(tài)改變是數(shù)據(jù)100次汉额,其實它只會應(yīng)用最后一次改變曹仗,如果沒有這種機制,DOM就要重繪100次蠕搜,這固然是一個很大的開銷怎茫。
Vue會根據(jù)當(dāng)前瀏覽器環(huán)境優(yōu)先使用原生的Promise.then和MutationObserver,如果都不支持妓灌,就會采用setTimeout代替轨蛤。
知道了Vue異步更新DOM的原理,上面的示例的報錯也就不難理解了虫埂。
事實上祥山,在執(zhí)行this.showDiv=true;時,div仍然還是沒有被創(chuàng)建出來掉伏,知道下一個Vue事件循環(huán)時缝呕,才開始創(chuàng)建。$nextTick就是用來知道什么時候DOM更新完成的斧散,上面的示例改為:
<div id="app">
<div id="div" v-if="showDiv">這是一段文本</div>
<button @click="getText">獲取div內(nèi)容</button>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
showDiv:false
},
methods:{
getText:function(){
this.showDiv = true;
this.$nextTick(function(){
var text = document.getElementById('div').innerHTML;
console.log(text)
})
}
}
})
</script>
這時在點擊按鈕供常,控制臺就打印出div的內(nèi)容“這是一段文本”了。
理論上鸡捐,我們應(yīng)該不用去主動操作DOM栈暇,因為Vue的核心思想就是數(shù)據(jù)驅(qū)動DOM,但在很多業(yè)務(wù)里箍镜,我們避免不了會使用一些第三方庫源祈,比如popper.js煎源、swiper等,這些基于原生javascript的庫都有創(chuàng)建和更新及銷毀的完整生命周期新博,與Vue配合使用時,就要利用好$nextTick脚草。
- X-Template赫悄。如果你沒有使用webpack、gulp等工具馏慨,試想一下你的組件template的內(nèi)容很冗長埂淮、復(fù)雜,如果都在JavaScript里拼接字符串写隶,效率很低倔撞。Vue提供了另一種定義模板的方式,在<script>標(biāo)簽里使用 text/x-template類型慕趴,并且指定一個id痪蝇,將這個id賦給template。示例:
<div id="app">
<my-component></my-component>
<script type="text/x-template" id="my-component">
<div> 這是組件的內(nèi)容 </div>
</script>
</div>
<script>
Vue.component('my-component',{
template:'#my-component'
})
var app = new Vue({
el:'#app'
})
</script>
在<script>標(biāo)簽里冕房,可以愉快地寫HTML代碼躏啰,不用考慮換行等問題。
Vue的初衷并不是濫用它耙册,因為它將模板和組件的其他定義隔離了给僵。
我們后續(xù)會使用webpack來編譯.vue的單文件,從而優(yōu)雅地解決HTML書寫的問題详拙。
30.手動掛在實例帝际。我們現(xiàn)在所創(chuàng)建的實例都是通過new Vue()的形式創(chuàng)建出來的。在一些非常特殊的情況下饶辙,我們需要動態(tài)地去創(chuàng)建Vue實例蹲诀,Vue提供了Vue.extend 和 mount()手動地掛載一個未掛載的實例守屉。
這個方法返回實例自身,因而可以鏈?zhǔn)秸{(diào)用其他實例方法蒿辙。示例:
<div id="mount-div">
</div>
<script>
var MyComponent = Vue.extend({
template:'<div>Hello:{{name}}</div>',
data:function(){
return {
name:'Aresn'
}
}
});
new MyComponent().$mount('#mount-div')
</script>
運行后拇泛,id為mount-div的div元素會被替換為組件MyComponent的template的內(nèi)容:
<div>Hello:Aresn</div>
除了這種寫法外滨巴,以下兩種寫法也是可以的:
new MyComponent({
el:'#mount-div'
});
//或者 ,在文檔之外渲染并且隨后掛載
var component = new MyComponent().$mount();
document.getElementById('mount-div').appendChild(component.$el);
手動掛載實例(組件)是一種比較極端的高級用法俺叭,在業(yè)務(wù)中幾乎用不到恭取,只在開發(fā)一些復(fù)雜的獨立組件時可能會使用,所以只做了解就好熄守。