一 自定義v-model屬性
vue在表單等domm模型中默認綁定了v-model的特性,而父組件中封裝了有input的子組件,那么如何使父子組件都能進行雙向綁定呢?這里提供一個案例:
父組件有一個custom-model子組件:
<template>
<div>
<p>{{name}}</p>
<custom-model v-model="name" />
</div>
</template>
<script>
import customModel from './CustomModel';
export default {
components: {
customModel
},
data() {
return {
name: ''
}
}
}
</script>
子組件custom-model:
<template>
<input type="text"
:value="text1"
@input="$emit('change1', $event.target.value)"
/>
<!-- 上面的input使用了:value而非v-model -->
<!-- 上面的change1和model.event對應(yīng)起來 -->
<!-- text1屬性對應(yīng)起來 -->
</template>
<script>
export default {
model: {
prop: 'text1',
event: 'change1'
},
props: {
text1: String,
default() {
return ''
}
}
}
</script>
父組件中的子組件自定義v-model,該值會與子組件model中的prop進行雙向數(shù)據(jù)綁定,同時提供event作為事件名進行事件注冊。
二 $nextTick
由于vue是異步渲染的,所以在修改vue的data等數(shù)據(jù)之后,等所有函數(shù)執(zhí)行完才會進行dom渲染,所以在函數(shù)執(zhí)行中,要在dom渲染后執(zhí)行外面要包裹一層$nextTick,下面有一個案例:
<template>
<div>
<ul ref="ul1">
<li v-for="(item,index) in list" :key="index">
{{item}}
</li>
</ul>
<button @click="add">add</button>
</div>
</template>
<script>
export default {
data() {
return {
list: ['a', 'b', 'c']
}
},
methods: {
add() {
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
// 異步渲染 $nextTick等待dom渲染完成后執(zhí)行
// 頁面渲染時將會對data的修改做整合,多次data修改只會修改一次
this.$nextTick(() => {
const ul1 = this.$refs.ul1
console.log(ul1.childNodes.length)
})
}
}
}
</script>
如果去掉this.$nextTick,在函數(shù)中打印的dom長度就不是6而是3,可以嘗試一下。
三 slot插槽
高級的插槽使用作用域插槽和具名插槽,案例:
父組件
<template>
<slot-name>
<!-- 作用域插槽 -->
<template v-slot="slotProps">
{{slotProps.slotData.name}}
</template>
<!-- 具名插槽 -->
<template v-slot:footer>
<p>aaa</p>
</template>
</slot-name>
</template>
<script>
import slotName from './slotName';
export default {
components: {
slotName
}
}
</script>
子組件
<template>
<div>
<a :href="website.url">
<slot :slotData="website">
哈哈哈
</slot>
</a>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
data() {
return {
website: {
url: 'http://www.baidu.com',
name: '百度'
}
}
}
}
</script>
子組件在slot中傳入slotData屬性和對應(yīng)的value,到父組件v-slot的名稱.slotData屬性就是子組件website的值,這里注意一下在具名插槽footer里的插槽不能使用slotData的值,所以交作用域插槽,只是在這個slot內(nèi)可以使用的變量
四 動態(tài)組件,異步組件
動態(tài)組件:
<Component :is="'text'" />
在子組件不確定的情況下,通過字符串傳參表示所對應(yīng)的類的名稱,組件能夠直接渲染出來,照常引用、注冊組件,使用component標簽并添加:is屬性,屬性值為組件名
異步組件:
普通組件采用import xx from xx的方式加載,為同步加載,當項目需要引入很大的組件時,同步加載性能會十分差,體驗也很不理想,所以此時需要采用異步加載組件的方法。
方法:直接在components里定義組件名,使用import()方法
export default{
components:{
${組件名}:()=>import(${組件路徑})
}
}
能夠在組件初始化時不用渲染而等到需要使用的時候再進行渲染,在路由的配置中經(jīng)常使用。
五 keep-alive
使用場景:頻繁切換,不需要重復(fù)渲染的組件
被包裹的組件會被緩存,在頻繁切換但不需要重復(fù)渲染的情況下使用,通常是vue其中一個性能優(yōu)化的方向
用法:需要頻繁切換的組件外層添加keep-alive標簽,添加后組件切換時不會被銷毀,會緩存起來,大幅提高渲染性能。
案例:
這是父組件,會同時切換兩個組件
<template>
<div>
<keep-alive>
<template v-if="data === 'c'">
<c />
</template>
<template v-if="data === 'd'">
<d />
</template>
</keep-alive>
<button @click="handle">提交</button>
</div>
</template>
<script>
import c from './c';
import d from './d';
export default {
components: {
c,
d
},
data() {
return {
data: 'c'
}
},
methods: {
handle() {
this.data === 'c' ? this.data = 'd' : this.data = 'c'
}
}
}
</script>
其中c組件和d組件基本相同只展示其中一個
<template>
<div>c</div>
</template>
<script>
export default {
mounted() {
console.log('c mounted')
},
destroyed() {
console.log('c destoryed')
}
}
</script>

如果沒有keep-alive,那么切換的時候destoryed函數(shù)也會執(zhí)行,但是有的情況destoryed函數(shù)就不執(zhí)行了,原因就在于keep-alive會緩存組件,組件在其中不會銷毀。
六 mixin
組件抽離公共邏輯
多個組件有相同的邏輯可以抽離出來的時候就可以使用。但是目前mixin并不是完美的解決方案,會有一些問題?,F(xiàn)在的Vue3意在解決這些問題。
案例:
<template>
<div>
{{a}}--{}
<button @click="handle">提交</button>
</div>
</template>
<script>
import myMixins from './mixins';
export default {
mixins: [ myMixins ],
data() {
return {
a: 'aaa'
}
}
}
</script>
export default {
data() {
return {
b: 'bbb'
}
},
methods: {
handle() {
console.log(this.b)
}
}
}
mixin目前存在的缺點:
- 變量來源不明確,不利于閱讀。
- 多mixin可能會造成命名沖突。
- mixin可能存在多對多的關(guān)系,復(fù)雜度較高。
七 watch進階
從我們剛開始學(xué)習Vue的時候,對于偵聽屬性,都是簡單地如下面一般使用:
watch:{
a(){
//doSomething
}
}
實際上,Vue對watch提供了很多進階用法。
handler函數(shù)
以對象和handler函數(shù)的方式來定義一個監(jiān)聽屬性,handler就是處理監(jiān)聽變動時的函數(shù):
watch:{
a:{
handler:'doSomething'
}
},
methods:{
doSomething(){
//當 a 發(fā)生變化的時候,做些處理
}
}
handler有啥用?是多此一舉么?用途主要有兩點:
- 將處理邏輯抽象出去了,以method的方式被復(fù)用
- 給定義下面兩個重要屬性留出了編寫位置
deep屬性
當watch的是一個Object類型的數(shù)據(jù),如果這個對象內(nèi)部的某個值發(fā)生了改變,并不會觸發(fā)watch動作!
也就是說,watch默認情況下,不監(jiān)測內(nèi)部嵌套數(shù)據(jù)的變動。但是很多情況下,我們是需要監(jiān)測的!
為解決這一問題,就要使用deep屬性:
watch:{
obj:{
handler:'doSomething',
deep:true
}
},
methods:{
doSomething(){
//當 obj 發(fā)生變化的時候,做些處理
}
}
deep屬性默認為false,也就是我們常用的watch模式。
immediate屬性
watch 的handler函數(shù)通常情況下只有在監(jiān)聽的屬性發(fā)生改變時才會觸發(fā)。
但有些時候,我們希望在組件創(chuàng)建后,或者說watch被聲明和綁定的時候,立刻執(zhí)行一次handler函數(shù),這就需要使用immediate屬性了,它默認為false,改為true后,就會立刻執(zhí)行handler。
watch:{
obj:{
handler:'doSomething',
deep:true,
immediate:true
}
},
methods:{
doSomething(){
//當 obj 發(fā)生變化的時候,做些處理
}
}
同時執(zhí)行多個方法
使用數(shù)組可以設(shè)置多項,形式包括字符串、函數(shù)、對象
watch: {
// 你可以傳入回調(diào)數(shù)組,它們會被逐一調(diào)用
a: [
'handle1',
function handle2 (val, oldVal) { /* ... */ },
{
handler: function handle3 (val, oldVal) { /* ... */ },
/* ... */
}
],
}