keep-alive

keep-alive缓存状态

keep-alive 标签为 <keep-alive></keep-alive>,是 Vue 内置的一个组件,可以使被包含的组件保留状态或避免重新渲染。在之前 7.4 节提到过复用的概念,这里有些类似,但是又不完全一样,当 keep-alive 应用在 <route-view> 上时,导航的切换会保留切换之前的状态,如示例代码7-11-1所示。

示例代码7-13-1 keep-alive
<div id="app">
    <p>
        <router-link to="/page">page</router-link>
        <router-link to="/user">user</router-link>
    </p>
    <router-view v-slot="{ Component }">
        <keep-alive :include="['page']">
            <component :is="Component"></component>
        </keep-alive>
    </router-view>
</div>
// 创建 User 组件
const User = {
    template: '<div><input type="range" /></div>'
}

// 创建 Page 组件
const Page = {
    name: 'page',
    template: '<div><input type="text" /></div>'
}

// 设置路由信息
const router = VueRouter.createRouter({
    history: VueRouter.createWebHashHistory(),
    routes: [
        { path: '/page', component: Page },
        { path: '/user', component: User },
    ]
})

const app = Vue.createApp({})
app.use(router)
app.mount("#app")

在上面的示例代码中补全 HTML 内容和 Script 内容后,可以在浏览器中运行。我们分别在 UserPage 组件中的 template 定义了文本输入框和滑动选择器,当输入文字或者调整滑块位置切换回来之后,这些状态都被保存了下来,如图7-4所示。

image 2024 02 23 17 54 24 565
Figure 1. 图7-4 keep-alive缓存

注意,在 Vue Router 4 中,<keep-alive> 必须通过 v-slot 插槽才能应用在 <router-view> 上,同时需要借助动态组件 <component>,v-slot 的第二个参数 route 则提供了当前的路由对象,可以借助其传递一些路由参数,或者是做一些逻辑判断,代码如下:

// 路由组件传参
<router-view v-slot="{ Component, route }">
    <component :is="Component" v-bind="route.params"></component>
</router-view>
// 逻辑判断显示404页面
<router-view v-slot="{ Component, route }">
    <component v-if="route.matched.length > 0" :is="Component"/>
    <div v-else>Not Found</div>
</router-view>

<router-view> 也是一个组件,如果直接被包含在 <keep-alive> 里面,所有路径匹配到的视图组件都会被缓存,也就是说如果只对某个或者某几个路径的路由进行缓存,<keep-alive> 也支持 include/exclude 设置项,如示例代码7-13-2所示。

<router-view v-slot="{ Component }">
    <keep-alive :include="['page']">
        <component :is="Component"></component>
    </keep-alive>
</router-view>
...
const User = {
    name:'user',
    template: '<div><input type="range" /></div>',
}
const Page = {
    name:'page',
    template: '<div><input type="text" /></div>',
}

上面的代码中,只有 page 组件的内容会被缓存。include/exclude 可以设置单个字符串或者正则表达式,也可以是一个由字符串或正则表达式组成的数组,匹配的内容是组件名称,include 表示需要缓存的组件,exclude 表示不需要缓存的组件。这里需要注意组件名称是组件的 name 属性,不是在设置路由信息时命名路由的 name

{ path: '/page', component: Page ,name:'page' }// 不是这个name

利用元数据meta控制keep-alive

有时,在不想通过 name 来设置缓存的组件时(例如在有些应用场合,无法提前得知组件的名称),也可以利用之前讲解的元数据 meta 来设置是否需要缓存,如示例代码7-13-3所示。

<div id="app">
    <p>
        <router-link to="/page">page</router-link>
        <router-link to="/user">user</router-link>
    </p>
    <router-view v-slot="{ Component }">
        <keep-alive :include="includeList">
            <component :is="Component"></component>
        </keep-alive>
    </router-view>
</div>
...
// 创建 User 组件
const User = {
    name: 'user',
    template: '<div><input type="range" /></div>'
}

// 创建 Page 组件
const Page = {
    name: 'page',
    template: '<div><input type="text" /></div>'
}

// 设置路由信息
const router = VueRouter.createRouter({
    history: VueRouter.createWebHashHistory(),
    routes: [
        {
            path: '/page',
            component: Page,
            name: 'page', // 需要配置命名路由和组件名称保持一致
            meta: {
                keepAlive: false
            }
        },
        {
            path: '/user',
            component: User,
            name: 'user', // 需要配置命名路由和组件名称保持一致
            meta: {
                keepAlive: true
            }
        },
    ]
})

const app = Vue.createApp({
    data() {
        return {
            includeList: []
        }
    },
    watch: {
        '$route'(to) {
            // 监听路由变化,把配置路由中 keepAlive 为 true 的 name 添加到 include 动态数组中
            if (to.meta.keepAlive && this.includeList.indexOf(to.name) === -1) {
                this.includeList.push(to.name)
            }
        }
    }
})
app.use(router)
app.mount("#app")

注意,还需要借助 include 来实现,只是 include 的值是依据 meta 中的 keepAlive 属性来动态添加的,同时需要配置命名路由和组件名称保持一致。

当把 <keep-alive> 应用在 <router-view> 上进行路由切换时,实际上组件是不会被销毁的,例如从 User 切换到 Page,除了第一次之外,UserPage 的生命周期方法(例如 createdmounted 等)都不会触发。但是如果没有使用 keep-alive 进行缓存,那么就相当于进行路由切换时,组件都被销毁了,当切换返回时,组件都会被重新创建,当然组件的生命周期方法都会被执行。可以使用下面的代码来进行验证。

const User = {
    name:'user',
    template: '<div><input type="range" /></div>',
    created(){
        console.log('created')  // created生命周期
    },
    mounted(){
        console.log('mounted')       // mounted生命周期
    }
}
const Page = {
    name:'page',
    template: '<div><input type="text" /></div>',
    created(){
        console.log('created')       // created生命周期
    },
    mounted(){
        console.log('mounted')       // mounted生命周期
    }
}

但是,在组件生命周期方法中,有两个特殊的方法:activateddeactivatedactivated 表示当 vue-router 的页面被打开时,会触发这个钩子函数;deactivated 表示当 vue-router 的页面被关闭时,会触发这个钩子函数。有了这两个方法,就可以在组件中得到页面切换的时机,如示例代码7-13-4所示。

示例代码7-13-4 activated方法和deactivated方法的使用
const User = {
    template: '<div><input type="range" /></div>',
    activated(){
        console.log('activated')
    },
    deactivated(){
        console.log('deactivated')
    }
}
const Page = {
    template: '<div><input type="text" /></div>',
    activated(){
        console.log('activated')
    },
    deactivated(){
        console.log('deactivated')
    }
}

除了使用组件生命周期方法之外,使用组件内的守卫方法 beforeRouteEnterbeforeRouteLeave 也可以达到相同的效果。注意之前讲的复用问题,路由切换时需要两个不同的组件才可以使用。<keep-alive> 不仅在 vue-router 中应用得比较广泛,在一般的组件中也是可以使用的。