一、什么是組件(Component)?
組件(Component)是Vue.js最強(qiáng)大的功能之一。組件可以擴(kuò)展HTML元素,封裝可以重用的代碼。在較高層面上,組件是自定義元素,Vue.js的編譯器為它添加特殊功能。在有些情況下,組件也可以表現(xiàn)為用is特性進(jìn)行了擴(kuò)展的原生HTML元素。
所有Vue組件同事也都是Vue實例,所以可以接受相同的選項對象(除了一些根級特有的選項)并且提供相同的生命周期鉤子。
二、使用組件
1.全局組件
我們知道,創(chuàng)建一個Vue實例可以:
new Vue({
el: '#some-element',
// 選項
})
全局注冊組件,可以使用Vue.component(tagName, options)。 比如:
Vue.component('my-component', {
// 選項
})
請注意,對于自定義標(biāo)簽的命名 Vue.js 不強(qiáng)制遵循 W3C 規(guī)則 (小寫,并且包含一個短杠),盡管這被認(rèn)為是最佳實踐。
組件在注冊之后,便可以作為自定義元素:<my-component></my-component>使用了。
注意確保在初始化根實例之前注冊組件:
在初始化根實例之前注冊組件這句話就是指的是:
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
var vm = new Vue({
el: '#box'
})
這樣的順序是對的,而
var vm = new Vue({
el: '#box'
})
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
這樣是錯的。
<div id="example">
<my-component></my-component>
</div>
// 注冊
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
// 創(chuàng)建根實例
new Vue({
el: '#example'
})
我們要注意,一般選項中需要添加template,就是HTML的構(gòu)造。
渲染為:
<div id="example">
<div>A custom component!</div>
</div>
2.局部注冊
不必把每個組件都注冊到全局。你可以通過某個 Vue 實例/組件的實例選項 components 注冊僅在其作用域中可用的組件:
var Child = {
template: '<div>A custom component!</div>'
}
var vm = new Vue({
el: '#box',
components: {
'my-component': Child
}
})
var vm = new Vue({
el: '#box2'
})
<div id="box">
<my-component></my-component>
</div>
<div id="box2">
<my-component></my-component>
</div>
Vue組件的全局注冊,可以在多個Vue實例中使用,如果是在Vue構(gòu)造器中局部注冊,那么只能在此Vue實例中使用。
那么在<div id="box">下面的<my-component></my-component>將不會渲染出來。
這種封裝也適用于其它可注冊的 Vue 功能,比如指令。
3.DOM模板解析注意事項
當(dāng)使用 DOM 作為模板的時候(例如,使用 el 選項來把 Vue 實例掛載到一個已有內(nèi)容的元素上),你會受到 HTML 本身的一些限制,因為 Vue 只有在瀏覽器解析,規(guī)范化之后才能獲取其內(nèi)容。尤其要注意,像 <ul> <ol>, <table>,<select> 這樣的元素里面允許包含的元素有限制,而另外一些像 <option> 這樣的元素只能出現(xiàn)在某些特定元素的內(nèi)部。
在自定義組件中使用這些受限制元素時候會導(dǎo)致一些問題,比如:
<table>
<my-row>...</my-row>
</table>
自定義組件 <my-row> 會被當(dāng)成無效的內(nèi)容,因此會導(dǎo)致錯誤的渲染結(jié)果。變通方式是使用特殊的 is 特性:
<table>
<tr is="my-row"></tr>
</table>
應(yīng)當(dāng)注意,如果使用來自以下來源之一的字符串模板,則沒有這些限制:
<script type="text/x-template">
JavaScript 內(nèi)聯(lián)模板字符串
.vue 組件
因此,請盡可能使用字符串模板。
-
data必須是函數(shù)
構(gòu)造 Vue 實例時傳入的各種選項大多數(shù)都可以在組件里使用。只有一個例外:data 必須是函數(shù)。實際上,如果你這么做:
Vue.component('my-component', {
template: '<span>{{ message }}</span>',
data: {
message: 'hello'
}
})
那么 Vue 會停止運行,并在控制臺發(fā)出警告,告訴你在組件實例中 data 必須是一個函數(shù)。但理解這種規(guī)則為何存在也是很有益處的,所以讓我們先作個弊:
<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
data參數(shù)中,一般是返回的js對象。比如:
data: function () {
return {
text: 'text',
name: 'Hello'
}
}
var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>', # 注意:這里是 += 不是 +
// 技術(shù)上 data 的確是一個函數(shù)了,因此 Vue 不會警告,
// 但是我們卻給每個組件實例返回了同一個對象的引用
data: function () {
return data
}
})
new Vue({
el: '#example-2'
})
由于這三個組件實例共享了同一個 data 對象,因此遞增一個 counter 會影響所有組件!這就錯了。我們可以通過為每個組件返回全新的數(shù)據(jù)對象來修復(fù)這個問題:
data: function () {
return {
counter: 0
}
}
現(xiàn)在每個 counter 都有它自己內(nèi)部的狀態(tài)了。
5.組件組合
組件設(shè)計初衷就是要配合使用的,最常見的就是形成父子組件的關(guān)系:組件 A 在它的模板中使用了組件 B。它們之間必然需要相互通信:父組件可能要給子組件下發(fā)數(shù)據(jù),子組件則可能要將它內(nèi)部發(fā)生的事情告知父組件。然而,通過一個良好定義的接口來盡可能將父子組件解耦也是很重要的。這保證了每個組件的代碼可以在相對隔離的環(huán)境中書寫和理解,從而提高了其可維護(hù)性和復(fù)用性。
在 Vue 中,父子組件的關(guān)系可以總結(jié)為 prop 向下傳遞,事件向上傳遞。父組件通過 prop 給子組件下發(fā)數(shù)據(jù),子組件通過事件給父組件發(fā)送消息。看看它們是怎么工作的。

三、Prop
1.使用Prop傳值
組件實例的作用域是孤立的。這意味著不能 (也不應(yīng)該) 在子組件的模板內(nèi)直接引用父組件的數(shù)據(jù)。父組件的數(shù)據(jù)需要通過 prop 才能下發(fā)到子組件中。
子組件要顯式地用 props 選項聲明它預(yù)期的數(shù)據(jù):
Vue.component('child', {
// 聲明 props
props: ['message'],
// 就像 data 一樣,prop 也可以在模板中使用
// 同樣也可以在 vm 實例中通過 this.message 來使用
template: '<span>{{ message }}</span>'
})
<div id="box">
<child message="hellow"></child>
<child message="hellow"></child>
<child message="123"></child>
</div>
2.駝峰法 vs 短橫線分隔法
HTML 特性是不區(qū)分大小寫的。所以,當(dāng)使用的不是字符串模板時,camelCase (駝峰式命名) 的 prop 需要轉(zhuǎn)換為相對應(yīng)的 kebab-case (短橫線分隔式命名):
Vue.js中是區(qū)分大小寫的。
Vue.component('child', {
// 在 JavaScript 中使用 camelCase
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})
<!-- 在 HTML 中使用 kebab-case -->
<child my-message="hello!"></child>
注意是自動轉(zhuǎn)換為的,我們需要在使用模板的時候<child my-message="hello!"></child>,注意到需要使用短橫線分隔符號。
3.動態(tài)Prop
與綁定到任何普通的 HTML 特性類似,我們可以使用 v-bind 來動態(tài)地將prop綁定到父組件的數(shù)據(jù)。
每當(dāng)父組件的數(shù)據(jù)變化時,該變化也會傳導(dǎo)給子組件:
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
你也可以使用 v-bind 的縮寫語法:
<child :my-message="parentMsg"></child>
如果你想把一個對象的所有屬性作為 prop 進(jìn)行傳遞,可以使用不帶任何參數(shù)的 v-bind (即用 v-bind 而不是 v-bind:prop-name)。例如,已知一個 todo 對象:
var vm = new Vue({
el: '#box',
data: {
todo: {
text: 'Learn Vue',
isComplete: false
}
}
})
<todo-item v-bind="todo"></todo-item>
等價于:
<todo-item
v-bind:text="todo.text"
v-bind:is-complete="todo.isComplete"
></todo-item>
eg:
<div id="box">
<child v-bind="todo"></child>
</div>
Vue.component('child', {
props: ['text', 'is-complete'],
template: '<span>{{ text }}</span>'
})
var vm = new Vue({
el: '#box',
data: {
todo: {
text: 'Learn Vue',
isComplete: false
}
}
})
或者直接傳遞一個對象:
<body>
<div id="box">
<child v-bind:todo="todo"></child>
</div>
Vue.component('child', {
props: ['todo'],
template: '<span>{{ todo.text }} {{ todo.isComplete }} </span>'
})
var vm = new Vue({
el: '#box',
data: {
todo: {
text: 'Learn Vue',
isComplete: false
}
}
})
從上面可以看出,一般我們還是這樣傳遞最好:
<child v-bind:todo="todo"></child>
不要v-bind="todo",這樣不是很方便樣。
4.字面量語法 vs 動態(tài)語法
字面量語法,就是直接傳遞字符串這樣:
初學(xué)者常犯的一個錯誤是使用字面量語法傳遞數(shù)值:
<!-- 傳遞了一個字符串 "1" -->
<comp some-prop="1"></comp>
因為它是一個字面量 prop,它的值是字符串 "1" 而不是一個數(shù)值。如果想傳遞一個真正的 JavaScript 數(shù)值,則需要使用 v-bind,從而讓它的值被當(dāng)作 JavaScript 表達(dá)式計算:
下面才是正確的:
<!-- 傳遞真正的數(shù)值 -->
<comp v-bind:some-prop="1"></comp>
5.單向數(shù)據(jù)流
Prop 是單向綁定的:當(dāng)父組件的屬性變化時,將傳導(dǎo)給子組件,但是反過來不會。這是為了防止子組件無意間修改了父組件的狀態(tài),來避免應(yīng)用的數(shù)據(jù)流變得難以理解。
另外,每次父組件更新時,子組件的所有 prop 都會更新為最新值。這意味著你不應(yīng)該在子組件內(nèi)部改變 prop。如果你這么做了,Vue 會在控制臺給出警告。
在兩種情況下,我們很容易忍不住想去修改 prop 中數(shù)據(jù):
1)Prop 作為初始值傳入后,子組件想把它當(dāng)作局部數(shù)據(jù)來用;
2)Prop 作為原始數(shù)據(jù)傳入,由子組件處理成其它數(shù)據(jù)輸出。
正確的應(yīng)對方式是:
1)定義一個局部變量,并且使用prop的值初始化它:
Vue.component('child', {
props: ['todo'],
template: '<span>{{ todo }} </span>',
data: function () {
return {
com_todo: this.todo + " ok"
}
}
})
注意:外部傳入了
todo進(jìn)來,那么現(xiàn)在又返回的有一個com_todo,現(xiàn)在組件中可以使用的數(shù)據(jù)就有兩個了:todo和com_todo。
在組件的template中,
可以這樣寫:
template: '<span>{{ todo }} </span>'
template: '<span>{{ this.todo }} </span>',
template: '<span>{{ com_todo }} </span>',
template: '<span>{{ this.com_todo }} </span>',
2)定義一個計算屬性(computed),來處理prop的值并且返回:
<div id="box">
<child v-bind:todo="123"></child>
</div>
Vue.component('child', {
props: ['todo'],
template: '<span>{{ computed_todo }} </span>',
data: function () {
return {
com_todo: this.todo + " ok"
}
},
computed: {
computed_todo: function () {
return this.todo + "2" # 注意這里不能 return todo + "2"
}
}
})
注意在 JavaScript 中對象和數(shù)組是引用類型,指向同一個內(nèi)存空間,如果 prop 是一個對象或數(shù)組,在子組件內(nèi)部改變它會影響父組件的狀態(tài)。
- Prop驗證
我們可以為組件的 prop 指定驗證規(guī)則。如果傳入的數(shù)據(jù)不符合要求,Vue 會發(fā)出警告。這對于開發(fā)給他人使用的組件非常有用。
要指定驗證規(guī)則,需要用對象的形式來定義 prop,而不能用字符串?dāng)?shù)組:
(我們之前的字符串?dāng)?shù)組形式是這樣的:props:['todo', 'doit'])
對象形式是:
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' }
}
},
// 自定義驗證函數(shù)
propF: {
validator: function (value) {
return value > 10
}
}
}
})
type 可以是下面原生構(gòu)造器:
String
Number
Boolean
Function
Object
Array
Symbol
四、 非Prop特性
所謂非 prop 特性,就是指它可以直接傳入組件,而不需要定義相應(yīng)的 prop。
盡管為組件定義明確的 prop 是推薦的傳參方式,組件的作者卻并不總能預(yù)見到組件被使用的場景。所以,組件可以接收任意傳入的特性,這些特性都會被添加到組件的根元素上。
例如,假設(shè)我們使用了第三方組件 bs-date-input,它包含一個 Bootstrap 插件,該插件需要在 input 上添加 data-3d-date-picker 這個特性。這時可以把特性直接添加到組件上 (不需要事先定義 prop):
<bs-date-input data-3d-date-picker="true"></bs-date-input>
添加屬性 data-3d-date-picker="true" 之后,它會被自動添加到 bs-date-input 的根元素上。
五、自定義事件
我們知道,父組件使用 prop 傳遞數(shù)據(jù)給子組件。但子組件怎么跟父組件通信呢?這個時候 Vue 的自定義事件系統(tǒng)就派得上用場了。
- 使用
v-on綁定自定義事件
每個Vue實例都實現(xiàn)了事件接口,即:
使用 $on(eventName) 監(jiān)聽事件 (也叫綁定事件)
使用 $emit(eventName) 觸發(fā)事件
Vue 的事件系統(tǒng)與瀏覽器的 EventTarget API 有所不同。盡管它們的運行起來類似,但是
$on和$emit并不是addEventListener和dispatchEvent的別名。
另外,父組件可以在使用子組件的地方直接用 v-on 來監(jiān)聽子組件觸發(fā)的事件。
不能用 $on 監(jiān)聽子組件釋放的事件,而必須在模板里直接用 v-on 綁定,參見下面的例子。
下面是一個例子:
(父組件在使用子組件的地方使用v-on監(jiān)聽子組件觸發(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="incrementCounter">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
incrementCounter: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
我們可以看到,這里有兩個v-on:click=事件綁定,注意這個increment就是自定義事件:
是使用this.$emit('increment')觸發(fā)這個事件的。
<button v-on:click="incrementCounter">{{ counter }}</button> # 子級(自己)
<button-counter v-on:increment="incrementTotal"></button-counter> # 父級
子組件已經(jīng)和它外部完全解耦了。它所做的只是報告自己的內(nèi)部事件,因為父組件可能會關(guān)心這些事件。請注意這一點很重要。
2.給組件綁定原生事件
有時候,你可能想在某個組件的根元素上監(jiān)聽一個原生事件??梢允褂?v-on 的修飾符 .native。例如:
<my-component v-on:click.native="doTheThing"></my-component>
3..sync修飾符
在一些情況下,我們可能會需要對一個 prop 進(jìn)行“雙向綁定”。事實上,這正是 Vue 1.x 中的 .sync 修飾符所提供的功能。當(dāng)一個子組件改變了一個帶 .sync 的 prop 的值時,這個變化也會同步到父組件中所綁定的值。這很方便,但也會導(dǎo)致問題,因為它破壞了單向數(shù)據(jù)流。由于子組件改變 prop 的代碼和普通的狀態(tài)改動代碼毫無區(qū)別,當(dāng)光看子組件的代碼時,你完全不知道它何時悄悄地改變了父組件的狀態(tài)。這在 debug 復(fù)雜結(jié)構(gòu)的應(yīng)用時會帶來很高的維護(hù)成本。
上面所說的正是我們在 2.0 中移除 .sync 的理由。但是在 2.0 發(fā)布之后的實際應(yīng)用中,我們發(fā)現(xiàn) .sync 還是有其適用之處,比如在開發(fā)可復(fù)用的組件庫時。我們需要做的只是讓子組件改變父組件狀態(tài)的代碼更容易被區(qū)分。
從 2.3.0 起我們重新引入了 .sync 修飾符,但是這次它只是作為一個編譯時的語法糖存在。它會被擴(kuò)展為一個自動更新父組件屬性的 v-on 監(jiān)聽器。
eg:
<comp :foo.sync="bar"></comp>
會被擴(kuò)展為:
<comp :foo="bar" @update:foo="val => bar = val"></comp>
當(dāng)子組件需要更新 foo 的值時,它需要顯式地觸發(fā)一個更新事件:
this.$emit('update:foo', newValue)
4.使用自定義事件的表單輸入組件
自定義事件可以用來創(chuàng)建自定義的表單輸入組件,使用 v-model 來進(jìn)行數(shù)據(jù)雙向綁定。要牢記:
<input v-model="something">
這不過是以下示例的語法糖:
<input
v-bind:value="something"
v-on:input="something = $event.target.value">
components: {
'm-header':MHeader
}
與
components: {
MHeader
}
是一樣的,只是省略掉了默認(rèn)的那個。
6.Vue的父組件與子組件之間的交互(數(shù)據(jù)和事件):
https://blog.csdn.net/qq_16559905/article/details/78761956
注意:子組件和父組件 函數(shù)的傳遞,是不需要寫在props中的。
子組件:
this.$emit('function', params)
父組件:
<sub-component @function="function_in_parent" ></sub-component>
這里的function_in_parent是在父組件中實現(xiàn)。