一、什么是組件(Component)?
組件(Component)是Vue.js最強(qiáng)大的功能之一攘滩。組件可以擴(kuò)展HTML元素,封裝可以重用的代碼纸泡。在較高層面上漂问,組件是自定義元素,Vue.js的編譯器為它添加特殊功能女揭。在有些情況下蚤假,組件也可以表現(xiàn)為用is
特性進(jìn)行了擴(kuò)展的原生HTML元素。
所有Vue組件同事也都是Vue實(shí)例吧兔,所以可以接受相同的選項(xiàng)對(duì)象(除了一些根級(jí)特有的選項(xiàng))并且提供相同的生命周期鉤子磷仰。
二、使用組件
1.全局組件
我們知道境蔼,創(chuàng)建一個(gè)Vue實(shí)例可以:
new Vue({
el: '#some-element',
// 選項(xiàng)
})
全局注冊(cè)組件灶平,可以使用Vue.component(tagName, options)
。 比如:
Vue.component('my-component', {
// 選項(xiàng)
})
請(qǐng)注意箍土,對(duì)于自定義標(biāo)簽的命名 Vue.js 不強(qiáng)制遵循 W3C 規(guī)則 (小寫逢享,并且包含一個(gè)短杠),盡管這被認(rèn)為是最佳實(shí)踐吴藻。
組件在注冊(cè)之后瞒爬,便可以作為自定義元素:<my-component></my-component>
使用了。
注意確保在初始化根實(shí)例之前注冊(cè)組件:
在初始化根實(shí)例之前注冊(cè)組件
這句話就是指的是:
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
var vm = new Vue({
el: '#box'
})
這樣的順序是對(duì)的,而
var vm = new Vue({
el: '#box'
})
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
這樣是錯(cuò)的侧但。
<div id="example">
<my-component></my-component>
</div>
// 注冊(cè)
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
// 創(chuàng)建根實(shí)例
new Vue({
el: '#example'
})
我們要注意矢空,一般選項(xiàng)中需要添加template
,就是HTML的構(gòu)造禀横。
渲染為:
<div id="example">
<div>A custom component!</div>
</div>
2.局部注冊(cè)
不必把每個(gè)組件都注冊(cè)到全局屁药。你可以通過某個(gè) Vue 實(shí)例/組件的實(shí)例選項(xiàng) components
注冊(cè)僅在其作用域中可用的組件:
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組件的全局注冊(cè),可以在多個(gè)Vue實(shí)例中使用燕侠,如果是在Vue構(gòu)造器中局部注冊(cè)者祖,那么只能在此Vue實(shí)例中使用。
那么在<div id="box">
下面的<my-component></my-component>
將不會(huì)渲染出來绢彤。
這種封裝也適用于其它可注冊(cè)的 Vue 功能,比如指令蜓耻。
3.DOM模板解析注意事項(xiàng)
當(dāng)使用 DOM 作為模板的時(shí)候(例如茫舶,使用 el
選項(xiàng)來把 Vue 實(shí)例掛載到一個(gè)已有內(nèi)容的元素上),你會(huì)受到 HTML 本身的一些限制刹淌,因?yàn)?Vue 只有在瀏覽器解析饶氏,規(guī)范化之后才能獲取其內(nèi)容。尤其要注意有勾,像 <ul>
<ol>
, <table>
疹启,<select>
這樣的元素里面允許包含的元素有限制,而另外一些像 <option>
這樣的元素只能出現(xiàn)在某些特定元素的內(nèi)部蔼卡。
在自定義組件中使用這些受限制元素時(shí)候會(huì)導(dǎo)致一些問題喊崖,比如:
<table>
<my-row>...</my-row>
</table>
自定義組件 <my-row>
會(huì)被當(dāng)成無效的內(nèi)容,因此會(huì)導(dǎo)致錯(cuò)誤的渲染結(jié)果雇逞。變通方式是使用特殊的 is
特性:
<table>
<tr is="my-row"></tr>
</table>
應(yīng)當(dāng)注意荤懂,如果使用來自以下來源之一的字符串模板,則沒有這些限制:
<script type="text/x-template">
JavaScript 內(nèi)聯(lián)模板字符串
.vue 組件
因此塘砸,請(qǐng)盡可能使用字符串模板节仿。
-
data
必須是函數(shù)
構(gòu)造 Vue 實(shí)例時(shí)傳入的各種選項(xiàng)大多數(shù)都可以在組件里使用。只有一個(gè)例外:data 必須是函數(shù)掉蔬。實(shí)際上廊宪,如果你這么做:
Vue.component('my-component', {
template: '<span>{{ message }}</span>',
data: {
message: 'hello'
}
})
那么 Vue 會(huì)停止運(yùn)行,并在控制臺(tái)發(fā)出警告女轿,告訴你在組件實(shí)例中 data 必須是一個(gè)函數(shù)箭启。但理解這種規(guī)則為何存在也是很有益處的,所以讓我們先作個(gè)弊:
<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
data參數(shù)中谈喳,一般是返回的js對(duì)象册烈。比如:
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 的確是一個(gè)函數(shù)了,因此 Vue 不會(huì)警告,
// 但是我們卻給每個(gè)組件實(shí)例返回了同一個(gè)對(duì)象的引用
data: function () {
return data
}
})
new Vue({
el: '#example-2'
})
由于這三個(gè)組件實(shí)例共享了同一個(gè) data 對(duì)象赏僧,因此遞增一個(gè) counter 會(huì)影響所有組件大猛!這就錯(cuò)了。我們可以通過為每個(gè)組件返回全新的數(shù)據(jù)對(duì)象來修復(fù)這個(gè)問題:
data: function () {
return {
counter: 0
}
}
現(xiàn)在每個(gè) counter 都有它自己內(nèi)部的狀態(tài)了淀零。
5.組件組合
組件設(shè)計(jì)初衷就是要配合使用的挽绩,最常見的就是形成父子組件的關(guān)系:組件 A 在它的模板中使用了組件 B。它們之間必然需要相互通信:父組件可能要給子組件下發(fā)數(shù)據(jù)驾中,子組件則可能要將它內(nèi)部發(fā)生的事情告知父組件唉堪。然而,通過一個(gè)良好定義的接口來盡可能將父子組件解耦也是很重要的肩民。這保證了每個(gè)組件的代碼可以在相對(duì)隔離的環(huán)境中書寫和理解唠亚,從而提高了其可維護(hù)性和復(fù)用性。
在 Vue 中持痰,父子組件的關(guān)系可以總結(jié)為 prop 向下傳遞灶搜,事件向上傳遞。父組件通過 prop 給子組件下發(fā)數(shù)據(jù)工窍,子組件通過事件給父組件發(fā)送消息割卖。看看它們是怎么工作的患雏。
三鹏溯、Prop
1.使用Prop傳值
組件實(shí)例的作用域是孤立的。這意味著不能 (也不應(yīng)該) 在子組件的模板內(nèi)直接引用父組件的數(shù)據(jù)淹仑。父組件的數(shù)據(jù)需要通過 prop 才能下發(fā)到子組件中丙挽。
子組件要顯式地用 props
選項(xiàng)聲明它預(yù)期的數(shù)據(jù):
Vue.component('child', {
// 聲明 props
props: ['message'],
// 就像 data 一樣,prop 也可以在模板中使用
// 同樣也可以在 vm 實(shí)例中通過 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)使用的不是字符串模板時(shí),camelCase (駝峰式命名) 的 prop 需要轉(zhuǎn)換為相對(duì)應(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>
注意是自動(dòng)轉(zhuǎn)換為的瞬浓,我們需要在使用模板的時(shí)候<child my-message="hello!"></child>
,注意到需要使用短橫線分隔符號(hào)蓬坡。
3.動(dòng)態(tài)Prop
與綁定到任何普通的 HTML 特性類似猿棉,我們可以使用 v-bind
來動(dòng)態(tài)地將prop綁定到父組件的數(shù)據(jù)。
每當(dāng)父組件的數(shù)據(jù)變化時(shí)屑咳,該變化也會(huì)傳導(dǎo)給子組件:
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
你也可以使用 v-bind 的縮寫語法:
<child :my-message="parentMsg"></child>
如果你想把一個(gè)對(duì)象的所有屬性作為 prop 進(jìn)行傳遞萨赁,可以使用不帶任何參數(shù)的 v-bind (即用 v-bind 而不是 v-bind:prop-name)。例如兆龙,已知一個(gè) todo 對(duì)象:
var vm = new Vue({
el: '#box',
data: {
todo: {
text: 'Learn Vue',
isComplete: false
}
}
})
<todo-item v-bind="todo"></todo-item>
等價(jià)于:
<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
}
}
})
或者直接傳遞一個(gè)對(duì)象:
<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 動(dòng)態(tài)語法
字面量語法慰安,就是直接傳遞字符串這樣:
初學(xué)者常犯的一個(gè)錯(cuò)誤是使用字面量語法傳遞數(shù)值:
<!-- 傳遞了一個(gè)字符串 "1" -->
<comp some-prop="1"></comp>
因?yàn)樗且粋€(gè)字面量 prop腋寨,它的值是字符串 "1" 而不是一個(gè)數(shù)值。如果想傳遞一個(gè)真正的 JavaScript 數(shù)值化焕,則需要使用 v-bind萄窜,從而讓它的值被當(dāng)作 JavaScript 表達(dá)式計(jì)算:
下面才是正確的:
<!-- 傳遞真正的數(shù)值 -->
<comp v-bind:some-prop="1"></comp>
5.單向數(shù)據(jù)流
Prop 是單向綁定的:當(dāng)父組件的屬性變化時(shí),將傳導(dǎo)給子組件撒桨,但是反過來不會(huì)查刻。這是為了防止子組件無意間修改了父組件的狀態(tài),來避免應(yīng)用的數(shù)據(jù)流變得難以理解凤类。
另外穗泵,每次父組件更新時(shí),子組件的所有 prop 都會(huì)更新為最新值谜疤。這意味著你不應(yīng)該在子組件內(nèi)部改變 prop火欧。如果你這么做了,Vue 會(huì)在控制臺(tái)給出警告茎截。
在兩種情況下,我們很容易忍不住想去修改 prop 中數(shù)據(jù):
1)Prop 作為初始值傳入后赶盔,子組件想把它當(dāng)作局部數(shù)據(jù)來用企锌;
2)Prop 作為原始數(shù)據(jù)傳入,由子組件處理成其它數(shù)據(jù)輸出于未。
正確的應(yīng)對(duì)方式是:
1)定義一個(gè)局部變量撕攒,并且使用prop的值初始化它:
Vue.component('child', {
props: ['todo'],
template: '<span>{{ todo }} </span>',
data: function () {
return {
com_todo: this.todo + " ok"
}
}
})
注意:外部傳入了
todo
進(jìn)來,那么現(xiàn)在又返回的有一個(gè)com_todo
烘浦,現(xiàn)在組件中可以使用的數(shù)據(jù)就有兩個(gè)了: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)定義一個(gè)計(jì)算屬性(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 中對(duì)象和數(shù)組是引用類型擦俐,指向同一個(gè)內(nèi)存空間,如果 prop 是一個(gè)對(duì)象或數(shù)組握侧,在子組件內(nèi)部改變它會(huì)影響父組件的狀態(tài)蚯瞧。
- Prop驗(yàn)證
我們可以為組件的 prop 指定驗(yàn)證規(guī)則。如果傳入的數(shù)據(jù)不符合要求品擎,Vue
會(huì)發(fā)出警告埋合。這對(duì)于開發(fā)給他人使用的組件非常有用。
要指定驗(yàn)證規(guī)則萄传,需要用對(duì)象的形式來定義 prop甚颂,而不能用字符串?dāng)?shù)組:
(我們之前的字符串?dāng)?shù)組
形式是這樣的:props:['todo', 'doit']
)
對(duì)象形式是:
Vue.component('example', {
props: {
// 基礎(chǔ)類型檢測(cè) (`null` 指允許任何類型)
propA: Number,
// 可能是多種類型
propB: [String, Number],
// 必傳且是字符串
propC: {
type: String,
required: true
},
// 數(shù)值且有默認(rèn)值
propD: {
type: Number,
default: 100
},
// 數(shù)組/對(duì)象的默認(rèn)值應(yīng)當(dāng)由一個(gè)工廠函數(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
Symbol
四、 非Prop特性
所謂非 prop 特性,就是指它可以直接傳入組件振诬,而不需要定義相應(yīng)的 prop蹭睡。
盡管為組件定義明確的 prop 是推薦的傳參方式,組件的作者卻并不總能預(yù)見到組件被使用的場(chǎng)景贷揽。所以棠笑,組件可以接收任意傳入的特性,這些特性都會(huì)被添加到組件的根元素上禽绪。
例如蓖救,假設(shè)我們使用了第三方組件 bs-date-input,它包含一個(gè) Bootstrap 插件印屁,該插件需要在 input 上添加 data-3d-date-picker 這個(gè)特性循捺。這時(shí)可以把特性直接添加到組件上 (不需要事先定義 prop):
<bs-date-input data-3d-date-picker="true"></bs-date-input>
添加屬性 data-3d-date-picker="true" 之后,它會(huì)被自動(dòng)添加到 bs-date-input 的根元素上雄人。
五从橘、自定義事件
我們知道,父組件使用 prop 傳遞數(shù)據(jù)給子組件础钠。但子組件怎么跟父組件通信呢恰力?這個(gè)時(shí)候 Vue 的自定義事件系統(tǒng)就派得上用場(chǎng)了。
- 使用
v-on
綁定自定義事件
每個(gè)Vue實(shí)例都實(shí)現(xiàn)了事件接口旗吁,即:
使用 $on(eventName) 監(jiān)聽事件 (也叫綁定事件)
使用 $emit(eventName) 觸發(fā)事件
Vue 的事件系統(tǒng)與瀏覽器的 EventTarget API 有所不同踩萎。盡管它們的運(yùn)行起來類似,但是
$on
和$emit
并不是addEventListener
和dispatchEvent
的別名很钓。
另外香府,父組件可以在使用子組件的地方直接用 v-on 來監(jiān)聽子組件觸發(fā)的事件。
不能用 $on 監(jiān)聽子組件釋放的事件码倦,而必須在模板里直接用 v-on 綁定企孩,參見下面的例子。
下面是一個(gè)例子:
(父組件在使用子組件的地方使用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
}
}
})
我們可以看到袁稽,這里有兩個(gè)v-on:click=
事件綁定勿璃,注意這個(gè)increment
就是自定義事件:
是使用this.$emit('increment')
觸發(fā)這個(gè)事件的。
<button v-on:click="incrementCounter">{{ counter }}</button> # 子級(jí)(自己)
<button-counter v-on:increment="incrementTotal"></button-counter> # 父級(jí)
子組件已經(jīng)和它外部完全解耦了运提。它所做的只是報(bào)告自己的內(nèi)部事件蝗柔,因?yàn)楦附M件可能會(huì)關(guān)心這些事件。請(qǐng)注意這一點(diǎn)很重要民泵。
2.給組件綁定原生事件
有時(shí)候癣丧,你可能想在某個(gè)組件的根元素上監(jiān)聽一個(gè)原生事件≌蛔保可以使用 v-on 的修飾符 .native胁编。例如:
<my-component v-on:click.native="doTheThing"></my-component>
3..sync
修飾符
在一些情況下厢钧,我們可能會(huì)需要對(duì)一個(gè) prop 進(jìn)行“雙向綁定”。事實(shí)上嬉橙,這正是 Vue 1.x 中的 .sync 修飾符所提供的功能早直。當(dāng)一個(gè)子組件改變了一個(gè)帶 .sync 的 prop 的值時(shí),這個(gè)變化也會(huì)同步到父組件中所綁定的值市框。這很方便霞扬,但也會(huì)導(dǎo)致問題,因?yàn)樗茐牧藛蜗驍?shù)據(jù)流枫振。由于子組件改變 prop 的代碼和普通的狀態(tài)改動(dòng)代碼毫無區(qū)別喻圃,當(dāng)光看子組件的代碼時(shí),你完全不知道它何時(shí)悄悄地改變了父組件的狀態(tài)粪滤。這在 debug 復(fù)雜結(jié)構(gòu)的應(yīng)用時(shí)會(huì)帶來很高的維護(hù)成本斧拍。
上面所說的正是我們?cè)?2.0 中移除 .sync 的理由。但是在 2.0 發(fā)布之后的實(shí)際應(yīng)用中杖小,我們發(fā)現(xiàn) .sync 還是有其適用之處肆汹,比如在開發(fā)可復(fù)用的組件庫(kù)時(shí)。我們需要做的只是讓子組件改變父組件狀態(tài)的代碼更容易被區(qū)分予权。
從 2.3.0 起我們重新引入了 .sync 修飾符昂勉,但是這次它只是作為一個(gè)編譯時(shí)的語法糖存在。它會(huì)被擴(kuò)展為一個(gè)自動(dòng)更新父組件屬性的 v-on 監(jiān)聽器扫腺。
eg:
<comp :foo.sync="bar"></comp>
會(huì)被擴(kuò)展為:
<comp :foo="bar" @update:foo="val => bar = val"></comp>
當(dāng)子組件需要更新 foo 的值時(shí)硼啤,它需要顯式地觸發(fā)一個(gè)更新事件:
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)的那個(gè)煞肾。
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
是在父組件中實(shí)現(xiàn)籍救。