引子
之前写Vue的时候,想实现一个Tab切换组件,我一般都是使用v-if和v-else,而且一直不觉得有什么不对,也没有想过优化,直到面试时被问到动态组件,才知道这种切换需求可以使用动态组件来做,所以写文章记录一下。本文将介绍两种方式的各自实现,以及keep-alive。
v-if、v-else方式
先来看一般的Tab页实现方法。
<template>
<div>
<button @click='fa'>A</button>
<button @click='fb'>B</button>
<aaa v-if="show"></aaa>
<bbb v-else></bbb>
</div>
</template>
<script>
//@ is an alias to /src
import aaa from './a';
import bbb from './b';
export default {
name: 'XXX',
components: { aaa, bbb },
data() {
return {
show: true
}
},
methods: {
fa() {
this.show = true
},
fb() {
this.show = false
}
},
}
</script>
<style lang="less">
</style>
这种方式可以实现需求,但是有多少个组件,我们就要写多少个v-else-if.代码量还是比较大的,而且组件多了之后,多层嵌套的循环也比较难看,那么多的判断从代码样式上难看,一堆逻辑在一起也难看清晰。
动态组件,is方式
下面我们看一下动态组件方式:
//父组件代码
<template>
<div>
<button @click='fa'>A</button>
<button @click='fb'>B</button>
<component :is="currentComp"></component>
</div>
</template>
<script>
//@ is an alias to /src
import aaa from './a';
import bbb from './b';
export default {
name: 'XXX',
data() {
return {
currentComp: aaa
}
},
methods: {
fa() {
this.currentComp = aaa
},
fb() {
this.currentComp = bbb
}
}
}
</script>
<style lang="less">
</style>
component标签是Vue的内置标签,里面有一个is属性,可以用作组件切换。 可以看到,使用动态组件方式,我们只需要写一个component标签,不需要多层的if-else嵌套。不仅如此,如果使用动态组件,import之后,是可以省略在component对象里注册逐渐这一步的,只需要在data里面接一下就可以了。
Keep-alive
相信看到这里,各位已经会使用简单的动态组件。现在有一个问题,动态组件会缓存吗? 使用v-if/else时,每次切换都会走到组件生命周期的destroyed()阶段,那么动态组件切换时会走到哪一个生命周期呢?我们不妨看看。
<script>
//@ is an alias to /src
export default{
name: 'A',
destroyed() {
console.log('销毁A')
},
data(){
return{
}
},
components:{
}
}
</script>
<script>
//@ is an alias to /src
export default{
name: 'A',
destroyed() {
console.log('销毁A')
},
data(){
return{
}
},
components:{
}
}
</script>
分别在A组件和B组件的destroyed阶段添加一段输出,看看是否销毁。
从实际情况来看,动态组件切换和v-if一样是会销毁切换的组件的。
这就带来一个问题,如果页面中有为提交的表单,用户切换之后会丢失,再切换回来又要重新输入,会带来糟糕的用户体验。 这是我们可以用keep-alive来保证组件不会销毁。
全部keep-alive
//父组件
<keep-alive>
<component :is="currentComp"/>
</keep-alive>
这样用到的组件就不会销毁了,切换到过的组件全部会被缓存,不会销毁,每个组件的表单内容都会保留。
局部keep-alive
但是,缓存大量的组件有时候会带来性能问题。如果我们只想缓存特定的组件要怎么做呢? 从官方的API文档里我们可以看到keep-alive提供了三个属性 include、exclude和max。
分别可以用来设置哪些缓存、哪些不缓存、最大缓存多少组件。
例如只缓存A组件:
//A组件
<script>
//@ is an alias to /src
export default{
name: 'aa1',
destroyed() {
console.log('销毁A')
},
data(){
return{
}
},
components:{
}
}
</script>
//父组件
<keep-alive include="aa1">
<component :is="currentComp"/>
</keep-alive>
我们首先需要设置A组件的name属性,这个属性可以供include和exclude使用。 include和exclude属性是支持正则的:
//父组件
<keep-alive :include="/a/">
<component :is="currentComp"/>
</keep-alive>
另外,如果是video之类的标签,设置keep-alive是会让其在后台继续播放的。
keep-alive原理
简单说一下就是cache对象、keys数组加插槽。
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
具体可以看vue源码——src/core/components文件夹下的keep-alive.js文件,组件皆对象嘛。