面试必备之vue中带参动态路由遇到keep-alive 擦出美妙火花╰( ̄▽ ̄)╮

面试官:keep-alive 会用吗? 有一个路由 /A/:id,用户在 A/001 和 A/002 来回切换,任意页面输入数据后不丢以后如何实现?

我:一般在页面A监听页面路由参数变化在离开前缓存数据,返回时反显回去。

面试官:什么?还要页面自己处理???。。。回去等消息吧

我:

640.gif

背景

在常规开发中 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 的组件是怎么样?

1686659750284.png

左边是没有设置 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>

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYrwB4Qk' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片