面试官:keep-alive 会用吗? 有一个路由 /A/:id,用户在 A/001 和 A/002 来回切换,任意页面输入数据后不丢以后如何实现?
我:一般在页面A监听页面路由参数变化在离开前缓存数据,返回时反显回去。
面试官:什么?还要页面自己处理???。。。回去等消息吧
我:
背景
在常规开发中 vue 使用 keep-alive 来缓存 router 达到页面数据不丢失的目的,一般有两个办法 写两个 script 或 安装三方包 并且在 script 上添加 name
常规场景没问题,动态路由因为是同一个页面name是一样的,直接就GG。。。。
一般添加 name 的方法
一般写法如下
<keep-alive :include="includeList">
<component :is="component" />
</keep-alive>
includeList 是一个 name 列表, 需要我们在页面组件上加上 name 跳转路由时加 name 添加进列表中才能缓存
比如
<script lang='ts'>
export default {
name:"XXX"
}
</script>
但遇到 script setup 写法时需要加两个 script 标签
<script lang='ts' setup >
...
</script>
<script lang='ts'>
export default {
name:"XXX"
}
</script>
或者 使用三方插件 unplugin-vue-setup-extend-plus 在 script 上添加 name 值
<script name='xxx' lang='ts' setup >
...
</script>
动态的 name?
理论上,keep-alive 要缓存必须每个 name 不一样才行,带参数的动态路由组件是同一个 vue-router, 会直接复用组件,这也意味着不会创建新的组件并且生命周期钩子不会被调用。
如果 name 值是动态的,那是不是就可以独立了呢??
说干就干,组件最终会转换为 VNode 先打印出 VNode 看看对比一下有 name 和 无name 的组件是怎么样?
左边是没有设置 name ,右边是设置了name
在vue中如果没有显式声明 name 这会将文件名作为 name 值, 但这个值不会被 keep-alive 识别到,所以没法用 ╮(╯﹏╰)╭
右边设置了 name ,多了个 name 字段。__name 字段消失了
我们是不是可以给他动态加上 name 不就可以了 ╰( ̄▽ ̄)╮
动态添加上name?
直接改组件上的 VNode 并不推荐,所以每次跳转页面都创建个 VNode 就好了,包裹一下 2233333
createVNode 函数,第一个参数可以传配置式的对象, render 字段用来渲染
为新创建的这个壳创建个 name,这个 name 可以自己定
既然要每个页面独立 name 那就按 url来命名吧,直接用 $route.fullPath 哈哈 ୧(๑•̀⌄•́๑)૭
关键代码:
createVNode({
name,// 包裹组件名称,自己定
render: () => cmp, // cmo 是要进入的page组件
});
然后把创建好的壳 给 keep-alive 恩。搞定。。。。。。。。。。
完整代码
<template>
<div>
<router-view v-slot="{ Component }">
<keep-alive :include="includeList">
<component :is="getComponent(Component, $route.fullPath)" />
</keep-alive>
</router-view>
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { ref, watch, createVNode, getCurrentInstance } from 'vue';
import type { VNode } from 'vue';
// 调用这个方法关闭清理掉缓存的页面
const closeTab = (({ url }) => {
// 是否有缓存
if (includeList.value.includes(url) === false) {
return;
}
console.log('缓存路由清理:', url);
// 有缓存则清空缓存
includeList.value = includeList.value.filter((o) => o !== url);
cacheList[url] = undefined;
});
const route = useRoute();
const includeList = ref<string[]>([]);
const app = getCurrentInstance();
// 将创建的 VNode 缓存起来,必须配合 keep-alive 不可单独使用,否则每次是新的
const cacheList: Record<string, VNode | undefined> = {};
watch(route, (to) => {
// 监听路由跳转
if (to.meta.keepAlive && includeList.value.indexOf(to.fullPath) === -1) {
includeList.value.push(to.fullPath);
}
});
/**
* 获取缓存组件并且为组件命名
* @param cmp
* @param name 组件名称
*/
const getComponent = (cmp: VNode, name: string) => {
// 不需要缓存的
if (includeList.value.includes(name) === false) {
return cmp;
}
// 复用之前的 VNode
if (cacheList[name]) {
return cacheList[name];
}
// 没初始化就进来了????
if (!app) {
return;
}
// 创建个包裹组件
const newCmp = createVNode({
// 包裹组件名称
name,
render: () => cmp,
});
// 添加上下文
newCmp.appContext = app.appContext;
// 加入缓存列表
cacheList[name] = newCmp;
return newCmp;
};
</script>