組件是vue最強大的功能之一,組件可以擴展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素, Vue.js 的編譯器為它添加特殊功能。在有些情況下,組件也可以是原生 HTML 元素的形式,以 is 特性擴展。
(一)注冊一個全局組件:(對于自定義標簽名,Vue.js不要求強制遵循W3C規則(小寫,并且包含一個短杠),盡管遵循這個規則比較好)
Vue.component('my-component',{
//內容
})
組件在注冊之后,便可以在父實例的模塊中以自定義元素
<my-component></my-component>
的形式使用了,
重點:必須要確保在初始化根實例之前注冊了組件
舉例:
<div id="app">
<my-component></my-component>
</div>
//全局注冊
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
//初始化根實例
new Vue({
el: "#app",
data: {
}
});
渲染結果:
<div id="app">
<div>A custom component!</div>
</div>
一般在項目中,不必在全局注冊每個組件。通過使用組件實例選項注冊,可以使組件僅在另一個實例/組件的作用域中可用:
var Child = {
template: '<div>A custom component!</div>'
}
new Vue({
el: "#app",
data: {
},
components: {
'my-component': Child
}
});
(二)DOM模板解析說明:
當使用 DOM 作為模版時(例如,將 el 選項掛載到一個已存在的元素上), 你會受到 HTML 的一些限制,因為 Vue 只有在瀏覽器解析和標準化 HTML 后才能獲取模版內容。尤其像這些元素 <ul> ,<ol>,<table> ,<select> 限制了能被它包裹的元素, 而一些像 <option> 這樣的元素只能出現在某些其它元素內部。
在自定義組件中使用這些受限制的元素時會導致一些問題,例如:
<table>
<my-row>...</my-row>
</table>
以上中,<mr-row>是會被認為是無效內容,因為table中只能包裹td,th,tr等元素,變通方案是使用特殊的is屬性:
<table>
<tr is="my-row"></tr>
</table>
(三)data必須是函數
我們先來看一個案例:
<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'
})
你會發現,三個按鈕是聯動的!!!
因為上面中,dada是一個函數,我們返回給每個組件的實例引用了同一個data對象,由于這三個組件共享了同一個data,因此增加一個counter會影響所有的組件
為了解決這個問題,我們需要將data設置為一個方法函數
data: function () {
return {
counter: 0
}
}
接下來,每個按鈕都會有了自己的狀態了
(四)構成組件
在Vue中,父子組件的關系可以總結為props down,events up。父組件通過props向下傳遞數據給子組件,子組件通過events給父組件發送消息
先來看看prop
組件實例的作用域是孤立的。這意味著不能(也不應該)在子組件的模板內直接引用父組件的數據。要讓子組件使用父組件的數據,我們需要通過子組件的props選項。
還是舉例說明:
子組件要顯示的用props選項聲明它期待獲得的數據:
Vue.component('my-component', {
//聲明props
props: ['message'],
//就像data一樣,prop可以用在模板內
//同樣也可以在vm實例中像"this.message" 這樣使用
template: '<div>{{ message }}</div>'
})
在使用時可以這樣傳入一個普通字符串
<div id="app">
<my-component message="hello!"></my-component>
</div>
特別注意:
HTML 特性是不區分大小寫的。所以,當使用的不是字符串模版,camelCased (駝峰式) 命名的 prop 需要轉換為相對應的 kebab-case (短橫線隔開式) 命名:
Vue.component('child', {
// camelCase in JavaScript
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})
<!-- kebab-case in HTML -->
<child my-message="hello!"></child>
動態prop
在模板中,要動態地綁定父組件的數據到子模板的props,與綁定到任何普通的HTML特性相類似,就是用 v-bind。每當父組件的數據變化時,該變化也會傳導給子組件:
<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動態語法
初學者常犯的一個錯誤就是使用字面量語法傳遞數值:
<!-- 傳遞了一個字符串 "1" -->
<comp some-prop="1"></comp>
因為它是一個字面 prop ,它的值是字符串 "1" 而不是number。如果想傳遞一個實際的number,需要使用 v-bind ,從而讓它的值被當作 JavaScript 表達式計算:
<!-- 傳遞實際的 number -->
<comp v-bind:some-prop="1"></comp>
單項數據流
prop是單向綁定的:當父組件的屬性變化時,將傳導給子組件,但是不會反過來。這是為了防止子組件無意修改了父組件的狀態--這會讓應用的數據流難以理解
另外,每次父組件更新時,子組件的所有prop都會更新為最新值,這意味著你不應該在子組件內改變prop
對于為什么會有修改prop中數據的沖動,VUE官方給出的解釋:
1.prop 作為初始值傳入后,子組件想把它當作局部數據來用;
2.prop 作為初始值傳入,由子組件處理成其它數據輸出。
對于這兩種原因,正確的應對方式時:
1.定義一個局部變量,并用prop的值初始化它:
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
2.定義一個計算屬性,處理 prop 的值并返回。
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意在 JavaScript 中對象和數組是引用類型,指向同一個內存空間,如果 prop 是一個對象或數組,在子組件內部改變它會影響父組件的狀態。
prop驗證
我們可以為組件的props指定驗證規格,如果傳入的數據不符合規格,vue會發出警告,當組件給其他人使用時,這很有用。
要指定驗證規格,需要用對象的形式,而不能用字符串數組
比如:
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也可以是一個自定義構造器函數,使用instanceof檢測。
(四)自定義事件
父組件是使用props傳遞數據給子組件,但是如果子組件要把數據傳遞回去,應該怎么做呢?那就是接下來要說的是自定義事件
使用v-on綁定自定義事件
來看一個例子
使用 $on(eventName) 監聽事件
使用 $emit(eventName) 觸發事件
<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
}
}
})
在本例中,子組件已經和它外部完全解耦了。它所做的只是報告自己的內部事件,至于父組件是否關心則與它無關。留意到這一點很重要。
使用自定義事件的表單輸入組件
自定義事件可以用來創建自定義的表單輸入組件,使用 v-model 來進行數據雙向綁定
非父子組件通信
有時候兩個組件也需要通信(非父子關系)在簡單的場景下,可以使用一個空的 Vue 實例作為中央事件總線:
var bus = new Vue()
// 觸發組件 A 中的事件
bus.$emit('id-selected', 1)
// 在組件 B 創建的鉤子中監聽事件
bus.$on('id-selected', function (id) {
// ...
})
(五)使用Slot分發內容
為了讓組件可以組合,我們需要一種方式來混合父組件的內容與子組件自己的模板,這個過程被稱作內容分發
(1)編譯作用域
父組件模板的內容在父組件作用域內編譯;子組件模板的內容在子組件作用域內編譯。
如果要綁定作用域內的指令到一個組件的根節點,你應當在組件自己的模板上做:
Vue.component('child-component', {
//有效,因為是在正確的作用域內
template: '<div v-show="someChildProperty">Child</div>',
data: function() {
return {
someChildProperty: true
}
}
})
(2)單個Slot
--------未完待續