一、什么是組件? 什么是組件化?
組件 (Component) 是 Vue.js 最強大的功能之一。組件可以擴展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素,Vue.js 的編譯器為它添加特殊功能。在有些情況下,組件也可以表現(xiàn)為用 is 特性進行了擴展的原生 HTML 元素。
簡單的說: 組件就是把一個很大的界面拆分為多個小的界面, 每一個小的界面就是一個組件
將大界面拆分成小界面就是組件化
組件系統(tǒng)讓我們可以用獨立可復(fù)用的小組件來構(gòu)建大型應(yīng)用,幾乎任意類型的應(yīng)用的界面都可以抽象為一個組件樹:
組件化的好處
可以簡化Vue實例的代碼.
可以提高代碼復(fù)用性
二、注冊組件
全局注冊
所有實例都能用全局組件。
1. 創(chuàng)建組件構(gòu)造器
通過 全局API:Vue.extend()
參數(shù):{Object} options
用法:使用基礎(chǔ) Vue 構(gòu)造器,創(chuàng)建一個“子類”。參數(shù)是一個 包含組件選項的對象。
let Profile = Vue.extend({
// 注意: 在創(chuàng)建組件指定組件的模板的時候, 模板只能有一個根元素
template: `
<div>
<img src="images/fm.jpg" alt="">
<p>我是描述信息</p>
</div>
`
});
2. 注冊已經(jīng)創(chuàng)建好的組件
Vue.component('my-component', Profile);
3. 使用注冊好的組件
<div id="app">
<my-component></my-component>
</div>
創(chuàng)建組件的簡化方式
- 在注冊組件的時候, 除了傳入一個組件構(gòu)造器以外, 還可以直接傳入一個 對象
Vue.component('my-component', {
template: `
<div>
<img src="images/fm.jpg" alt="">
<p>我是描述信息</p>
</div>
`
});
- 在編寫組件模板的時候, 除了可以在 字符串模板 中編寫以外, 還可以像
art-template一樣在 script 中編寫
<script id="info" type="text/html">
<div>
<img src="images/fm.jpg" alt="">
<p>我是描述信息</p>
</div>
</script>
- 在編寫組件模板的時候, 除了可以在 script 中編寫以外, vue還專門提供了一個編寫模板的標簽
template
<template id="info">
<div>
<img src="images/fm.jpg" alt="">
<p>我是描述信息</p>
</div>
</template>
上面兩種編寫模板的方式在創(chuàng)建的時候一定要記得加上 id, 在使用的時候也要加上 id名稱
Vue.component('my-component', {
template: '#info'
});
局部注冊
我們也可以在實例選項中注冊局部組件,這樣組件只能在這個實例中使用.
可以通過某個 Vue 實例/組件的實例選項 components 注冊僅在其作用域中可用的組件:
new Vue({
// ...
components: {
'my-component': {
template: '#info'
}
}
});
自定義全局組件特點:
在任何一個Vue實例控制的區(qū)域中都可以使用
自定義局部組件特點:
只能在自定義的那個Vue實例控制的區(qū)域中才可以使用
三、組件中的data和methods
Vue實例控制的區(qū)域相當(dāng)于一個大的組件, 在大組件中我們可以使用data和methods
而我們自定義的組件也是一個組件, 所以在自定義的組件中也能使用data和methods
1. Vue中使用data和methods
<div id="app">
<button @click="vueFn">vue-alert</button>
<p>{{vueMsg}}</p>
</div>
new Vue({
el: '#app',
methods: {
vueFn(){
alert('vue-Fn');
}
},
data: {
vueMsg: 'vue-Msg'
}
});
2. 自定義組件中使用data和methods
在自定義組件中不能像在vue實例中一樣直接使用data,而是必須通過返回函數(shù)的方式來使用data。
<template id="info">
<div>
<button @click="myFn">my-alert</button>
<p>{{myMsg}}</p>
</div>
</template>
Vue.component('my-component', {
template: '#info',
methods: {
myFn(){
alert('my-Fn');
}
},
data: function () {
return {
myMsg: 'my-Msg'
}
}
});
自定義組件中的data為什么是一個函數(shù)
因為自定義組件可以復(fù)用, 為了保證復(fù)用時每個組件的數(shù)據(jù)都是獨立的, 所以必須是一個函數(shù)
看下面這個例子:
// HTML
<div id="app">
<my-component></my-component>
<my-component></my-component>
<my-component></my-component>
</div>
<template id="info">
<div>
<button @click="add">增加</button>
<p>{{counter}}</p>
</div>
</template>
// JS
Vue.component('my-component', {
template: '#info',
data: function () {
return {
counter: 0
}
},
methods: {
add(){
this.counter++;
}
}
});
new Vue({
el: '#app',
});
運行結(jié)果: 點擊按鈕的時候只有自己按鈕下的數(shù)據(jù)會加1
組件中的data如果不是通過函數(shù)返回的, 那么多個組件就會共用一份數(shù)據(jù), 就會導(dǎo)致數(shù)據(jù)混亂。
組件中的data如果是通過函數(shù)返回的, 那么每創(chuàng)建一個新的組件, 都會調(diào)用一次這個方法,將這個方法返回的數(shù)據(jù)和當(dāng)前創(chuàng)建的組件綁定在一起, 這樣就有效的避免了數(shù)據(jù)混亂。
如果 Vue 沒有這條規(guī)則,點擊一個按鈕就會像影響到其它所有實例:那么上面的例子中的數(shù)據(jù)就會一起加1;
四、組件切換
1. 通過 v-if / v-else
對于普通的元素我們可以通過v-if來實現(xiàn)切換,對于組件我們也可以通過v-if來實現(xiàn)切換。
因為組件的本質(zhì)就是一個自定義元素。
// HTML
<div id="app">
<button @click="show=!show">toggle</button>
<my-component1 v-if="show"></my-component1>
<my-component2 v-else></my-component2>
</div>
<template id="info1">
<div>
<p>我是info1</p>
</div>
</template>
<template id="info2">
<div>
<p>我是info2</p>
</div>
</template>
<script>
Vue.component('my-component1', {
template: '#info1'
});
Vue.component('my-component2', {
template: '#info2'
});
new Vue({
el: '#app',
data: {
show: true
}
});
</script>
2. 通過動態(tài)組件
通過v-if/v-else-if/v-else確實能夠切換組件,但是在Vue中切換組件還有一種更專業(yè)的方式:
<component v-bind:is="需要顯示組件名稱"></component>
component我們稱之為動態(tài)組件, 也就是你讓我顯示誰我就顯示誰
通過使用 <component> 元素,動態(tài)地綁定到它的 is 特性
<div id="app">
<button @click="toggle">toggle</button>
<component :is="name"></component>
</div>
<template id="info1">
<div>
<p>我是info1</p>
</div>
</template>
<template id="info2">
<div>
<p>我是info2</p>
</div>
</template>
<script>
Vue.component('my-component1', {
template: '#info1'
});
Vue.component('my-component2', {
template: '#info2'
});
new Vue({
el: '#app',
data: {
show: true,
name: 'my-component1'
},
methods: {
toggle() {
this.show = !this.show;
this.name = this.name === 'my-component1' ? 'my-component2' : 'my-component1';
}
}
});
</script>
為什么可以通過v-if切換還要有component
因為component可以配合keep-alive來保存被隱藏組件隱藏之前的狀態(tài), 而v-if會重新渲染頁面, 所以不能保存之前的狀態(tài)
<keep-alive> 包裹動態(tài)組件時,會緩存不活動的組件實例,而不是銷毀它們。和 <transition> 相似,<keep-alive> 是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出現(xiàn)在父組件鏈中
<keep-alive>
<component :is="name"></component>
</keep-alive>
<template id="info1">
<div>
<input type="checkbox">
<p>我是info1</p>
</div>
</template>
這里可以記住復(fù)選框的選中狀態(tài)
如果我們需要頻繁的切換頁面,每次都是在組件的創(chuàng)建和銷毀的狀態(tài)間切換,這無疑增大了性能的開銷。
這個時候我們也可以使用Vue提供了動態(tài)組件的 緩存。keep-alive會在切換組件的時候緩存當(dāng)前組件的狀態(tài),等到再次進入這個組件,不需要重新創(chuàng)建組件,只需要從前面的緩存中讀取并渲染。
五、組件動畫
給組件添加動畫和過去給元素添加動畫一樣。
如果是單個組件就使用transition,如果是多個組件就使用transition-group。
// HTML
<div id="app">
<button @click="toggle">toggle</button>
<transition>
<component :is="name"></component>
</transition>
</div>
<template id="info1">
<div>
<p>我是info1</p>
</div>
</template>
<template id="info2">
<div>
<p>我是info2</p>
</div>
</template>
// CSS
.v-enter{
opacity: 0;
margin-left: 500px;
}
.v-enter-to{
opacity: 1;
}
.v-enter-active{
transition: all 3s;
}
.v-leave{
opacity: 1;
}
.v-leave-to{
opacity: 0;
}
.v-leave-active{
transition: all 3s;
margin-left: 500px;
}
// JS
<script>
Vue.component('my-component1', {
template: '#info1'
});
Vue.component('my-component2', {
template: '#info2'
});
new Vue({
el: '#app',
data: {
show: true,
name: 'my-component1',
},
methods: {
toggle(){
this.show = !this.show;
this.name = this.name === 'my-component1' ? 'my-component2' : 'my-component1';
}
}
});
</script>
效果:
在這個案例中可以發(fā)現(xiàn)一個問題: 兩個的話是同時執(zhí)行的.
默認情況下進入動畫和離開動畫是同時執(zhí)行的, 如果想一個做完之后再做另一個, 需要指定動畫的過渡模式.
過渡模式
過渡模式常常配合多個元素或者多個組件切換時使用,有如下兩種模式:
-
in-out:新元素先進行過渡,完成之后當(dāng)前元素過渡離開。(默認為該模式) -
out-in:當(dāng)前元素先過渡離開,離開完成后新元素過渡進入。
所以可以把上面的代碼改造一下, 就可以讓一個元素先出去, 另一個元素再進來
<transition mode='out-in'>
<component :is="name"></component>
</transition>
六、父子組件
在一個組件中又定義了其它組件就是父子組件。
其實局部組件就是最簡單的父子組件, 因為我們可以把Vue實例看做是一個大組件。
我們在Vue實例中定義了局部組件, 就相當(dāng)于在大組件里面定義了小組件, 所以局部組件就是最簡單的父子組件
1. 如何定義父子組件
前面講過, 自定義組件中可以使用data, 可以使用methods. 當(dāng)然自定義組件中也可以使用components,
所以我們也可以在自定義組件中再定義其它組件。
- 在全局父組件中定義子組件
Vue.component('father', {
template: '#father',
components: {
'son': {
template: '#son'
}
}
});
- 在局部父組件中定義子組件
new Vue({
el: '#app',
components: {
'father': {
template: '#father',
components: {
'son': {
template: '#son'
}
}
}
}
});
-
父子組件的使用
把自定義子組件放到自定義父組件中, 把自定義父組件放到Vue組件中
<div id="app">
<father></father>
</div>
<template id="father">
<div>
<p>我是父組件</p>
<son></son>
</div>
</template>
<template id="son">
<div>
<p>我是子組件</p>
</div>
</template>
2. 父子組件數(shù)據(jù)傳遞
在Vue中子組件是不能訪問父組件的數(shù)據(jù)的,如果子組件想要訪問父組件的數(shù)據(jù), 必須通過父組件傳遞。
如何傳遞數(shù)據(jù)
- 在父組件中通過
v-bind傳遞數(shù)據(jù)
傳遞格式: v-bind:自定義接收名稱 = "要傳遞數(shù)據(jù)"
<template id="father">
<div>
<p>{{msg}}</p>
<!--這里將父組件的msg通過parentmsg傳遞給了子組件-->
<son :parentmsg="msg"></son>
</div>
</template>
- 在子組件中通過
props接收數(shù)據(jù)
接收格式: props: ["自定義接收名稱"]
//...
components: {
'son': {
template: '#son',
// 這里通過parentmsg接收了父組件傳遞過來的數(shù)據(jù)
props: ['parentmsg']
}
}
如何使用數(shù)據(jù)
- 子組件使用父組件傳遞的數(shù)據(jù)
<template id="son">
<div>
<!--這里通過parentmsg使用了父組件傳遞過來的數(shù)據(jù)-->
<p>{{parentmsg}}</p>
</div>
</template>
3. 父子組件方法傳遞
在Vue中子組件是不能訪問父組件的方法的,如果子組件想要訪問父組件的方法, 必須通過父組件傳遞
如何傳遞方法
- 在父組件中通過
v-on傳遞方法
傳遞格式: v-on:自定義接收名稱 = "要傳遞方法"
<template id="father">
<div>
<button @click="say">父組件按鈕</button>
<!--這里通過parentsay將父組件的say方法傳遞給了子組件-->
<son @parentsay="say"></son>
</div>
</template>
- 在子組件中自定義一個方法
<template id="son">
<div>
<button @click="sonFn">子組件按鈕</button>
</div>
</template>
- 在自定義方法中通過
this.$emit('自定義接收名稱');觸發(fā)傳遞過來的方法
components: {
'son': {
template: '#son',
methods: {
sonFn(){
this.$emit('parentsay');
}
}
}
}
和傳遞數(shù)據(jù)不同, 如果傳遞的是方法, 那么在子組件中不需要接收。
但是需要在子組件中自定義一個方法, 直接使用自定義的方法.
并且還需要在子組件自定義的方法中通過
this.$emit("自定義接收的名稱")的方法來觸發(fā)父組件傳遞過來的方法
$emit( eventName, […args] ) 觸發(fā)事件
- {string} eventName 需要調(diào)用的函數(shù)名稱
- [...args] 給調(diào)用的函數(shù)傳遞的參數(shù)
觸發(fā)當(dāng)前實例上的事件。附加參數(shù)都會傳給監(jiān)聽器回調(diào)。
所以子組件可以通過這個方法給父組件傳遞參數(shù).
components: {
'son': {
template: '#son',
methods: {
sonFn(){
this.$emit('parentsay', 'son');
}
}
}
}
父組件接收參數(shù):
methods: {
say(data){
console.log(data);
}
}
4. 數(shù)據(jù)和方法的多級傳遞
在Vue中如果兒子想使用爺爺?shù)臄?shù)據(jù), 必須一層一層往下傳遞
在Vue中如果兒子想使用爺爺?shù)姆椒? 必須一層一層往下傳遞
七、組件中的命名
1. 注冊組件的時候使用了"駝峰命名", 那么在使用時需要轉(zhuǎn)換成"短橫線分隔命名"
例如: 注冊時: myFather -> 使用時: my-father
2. 在傳遞參數(shù)的時候如果想使用"駝峰名稱", 那么就必須寫"短橫線分隔命名"
例如: 傳遞時: parent-msg="msg" -> 接收時: props: ["parentMsg"]
3. 在傳遞參數(shù)的時候如果想使用"駝峰名稱", 那么就必須寫"短橫線分隔命名"
例如: @parent-say="say" -> this.$emit("parent-say");
八、插槽
默認情況下使用子組件時,在子組件中編寫的元素是不會被渲染的
如果子組件中有部分內(nèi)容是使用時才確定的, 那么我們就可以使用插槽
插槽就是在子組件中放一個"坑", 以后由父組件來"填"。
比如在下面這個例子中, 沒有使用插槽的話父組件在<son></son>內(nèi)編寫的內(nèi)容是無效的
<template id="father">
<div>
<son>
<!--如果沒有插槽, 父組件在這里編寫的內(nèi)容是無效的-->
</son>
</div>
</template>
默認情況下是不能在使用子組件的時候, 給子組件動態(tài)的添加內(nèi)容的
如果想在使用子組件的時候, 給子組件動態(tài)的添加內(nèi)容, 那么就必須使用插槽
1. 匿名插槽
沒有名字的插槽, 會利用使用時指定的內(nèi)容替換整個插槽
這里的slot標簽就是插槽, 插槽其實就是一個坑。只要有了這個坑, 那么以后使用者就可以根據(jù)自己的需要來填這個坑。
<template id="son">
<div>
<div>我是頭部</div>
<slot>我是默認的內(nèi)容</slot>
<div>我是底部</div>
</div>
</template>
插槽可以指定默認數(shù)據(jù), 如果使用者沒有填這個坑, 那么就會顯示默認數(shù)據(jù)。
如果使用者填了這個坑, 那么就會利用使用者坑的內(nèi)容替換整個插槽。
例如: 這個father組件在使用son組件的時候填了這個坑,那么就會用父組件坑的內(nèi)容覆蓋掉整個插槽.
所以最后的效果是:
<template id="father">
<div>
<!--需求: 在使用子組件的時候給子組件動態(tài)的添加一些內(nèi)容-->
<son>
<div>我是追加的內(nèi)容</div>
</son>
</div>
</template>
匿名插槽的特點:
有多少個匿名插槽, 填充的數(shù)據(jù)就會被拷貝幾份
<template id="son">
<div>
<div>我是頭部</div>
<slot>我是默認的內(nèi)容</slot>
<slot>我是默認的內(nèi)容</slot>
<div>我是底部</div>
</div>
</template>
效果圖:
雖然我們可以指定多個匿名插槽, 但是推薦只寫一個匿名插槽
2. 具名插槽
默認情況下有多少個匿名插槽, 我們填充的數(shù)據(jù)就會被拷貝多少份,這導(dǎo)致了所有插槽中填充的內(nèi)容都是一樣的。
那么如果我們想給不同的插槽中填充不同的內(nèi)容怎么辦呢?
這個時候就可以使用具名插槽
具名插槽的使用
- 通過插槽的
name屬性給插槽指定名稱
<template id="son">
<div>
<div>我是頭部</div>
<slot name="one">我是one默認的內(nèi)容</slot>
<slot name="two">我是two默認的內(nèi)容</slot>
<div>我是底部</div>
</div>
</template>
- 在使用時可以通過
slot="name"方式, 指定當(dāng)前內(nèi)容用于替換哪一個插槽
默認情況下填充的內(nèi)容是不會被填充到具名插槽中的,
只有給填充的內(nèi)容指定了要填充到哪一個具名插槽之后,
才會將填充的內(nèi)容填充到具名插槽中
<template id="father">
<div>
<son>
<!--這里通過slot屬性告訴Vue,當(dāng)前的內(nèi)容是要填充到哪一個插槽中的-->
<div slot="one">我是追加的內(nèi)容one</div>
<!--如果沒有指定要替換哪個插槽中的內(nèi)容, 則不會被替換-->
<div>我是追加的內(nèi)容two</div>
</son>
</div>
</template>
3. v-slot指令
v-slot指令是Vue2.6中用于替代slot屬性的一個指令
在Vue2.6之前, 我們通過slot屬性告訴Vue當(dāng)前內(nèi)容填充到哪一個具名插槽
從Vue2.6開始, 我們通過v-slot指令告訴Vue當(dāng)前內(nèi)容填充到哪一個具名插槽
格式: v-slot:插槽名稱
簡寫: #插槽名稱
注意: v-slot指令只能用在template標簽上
例如:
<son>
<template v-slot:one>
<div>我是追加的內(nèi)容one</div>
<div>我是追加的內(nèi)容one</div>
</template>
<template #two>
<div>我是追加的內(nèi)容two</div>
<div>我是追加的內(nèi)容two</div>
</template>
</son>
4. 作用域插槽
作用域插槽就是帶數(shù)據(jù)的插槽, 就是讓父組件在填充子組件插槽內(nèi)容時也能使用子組件的數(shù)據(jù)
如何使用作用域插槽
- 在
slot中通過v-bind:數(shù)據(jù)名稱="數(shù)據(jù)名稱"方式暴露數(shù)據(jù)
<template id="son">
<div>
<div>我是頭部</div>
<!--v-bind:names="names"作用: 將當(dāng)前子組件的names數(shù)據(jù)暴露給父組件-->
<slot v-bind:names="names"></slot>
<div>我是底部</div>
</div>
</template>
- 在父組件中通過
<template slot-scope="作用域名稱">接收數(shù)據(jù)
<template id="father">
<div>
<son>
<!--slot-scope="sonMsg"作用: 接收子組件插槽暴露的數(shù)據(jù)-->
<template slot-scope="sonMsg">
</template>
</son>
</div>
</template>
- 在父組件的
<template></template>中通過 作用域名稱.數(shù)據(jù)名稱 方式使用數(shù)據(jù)
<template slot-scope="sonMsg">
<div>{{sonMsg.names}}</div>
</template>
作用域插槽的應(yīng)用場景:
子組件提供數(shù)據(jù), 父組件決定如何渲染
5. v-slot 指令代替 slot-scope
在 2.6.0 中,我們?yōu)榫呙宀酆妥饔糜虿宀垡肓艘粋€新的統(tǒng)一的語法 (即 v-slot 指令)。
它取代了 slot 和 slot-scope
也就是說我們除了可以通過 v-slot 指令告訴Vue內(nèi)容要填充到哪一個具名插槽中,還可以通過v-slot指令告訴Vue如何接收作用域插槽暴露的數(shù)據(jù)
格式: v-slot:插槽名稱="作用域名稱"
簡寫: #插槽名稱="作用域名稱"
<!--匿名插槽的默認名稱是default-->
<template v-slot:default="sonMsg">
<div>{{sonMsg.names}}</div>
</template>