Vue系列(一):动态组件和Keep-alive初窥

534 阅读3分钟

引子

之前写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阶段添加一段输出,看看是否销毁。

image.png

从实际情况来看,动态组件切换和v-if一样是会销毁切换的组件的。

这就带来一个问题,如果页面中有为提交的表单,用户切换之后会丢失,再切换回来又要重新输入,会带来糟糕的用户体验。 这是我们可以用keep-alive来保证组件不会销毁。

全部keep-alive

//父组件

 <keep-alive>
            <component :is="currentComp"/>
        </keep-alive>

这样用到的组件就不会销毁了,切换到过的组件全部会被缓存,不会销毁,每个组件的表单内容都会保留。

局部keep-alive

但是,缓存大量的组件有时候会带来性能问题。如果我们只想缓存特定的组件要怎么做呢? 从官方的API文档里我们可以看到keep-alive提供了三个属性 include、exclude和max。

image.png 分别可以用来设置哪些缓存、哪些不缓存、最大缓存多少组件。 例如只缓存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文件,组件皆对象嘛。