错怪react的半年-聊聊keepalive

9,848 阅读5分钟

背景

在半年前的某一天,一个运行一年的项目被人提了建议,希望有一个tabs页面,因为他需要多个页面找东西。

我:滚,自己开浏览器标签页(此处吹牛逼)

项目经理:我审核的时候也需要看别人提交的数据是否正确,我也需要看,很多人提了建议

我:作为一个合格的外包,肯定以项目经理体验为主(狗头保命)

1 React的Keepalive

简单实现React KeepAlive不依赖第三方库(附源码)React KeepAlive 实现,不依赖第三方库,支持 - 掘金

神说要有光《React通关秘籍》

....还有一些没有收藏的keepalive实现

代码不想多赘述,我找了很多资料基本思路讲一下

基本是通过react-router来实现

  • 弄一个map来存储{path:<OutLet|useOutlet>}
  • 然后根据当前路由path来决定哪些组件需要渲染,不渲染的hidden
  • 然后在最外层布局套一层Tabs布局用react的context来传递

出现的问题是:

072932bf075694f67855441b3d7c883.jpg

就是当依赖项是一个公共数据的时候,useEffect会触发,如图片中的searchParams同名的key、存在state内存中的同名key之类的

详情页打开多个,地址栏清一色pkId的情况

使用的是ant design pro v6,也是看到有加配置就可以用Keepalive,大家可以掘金找找,我测试过也是有一样的问题,但是和目前能找到功能差不多了,免去自己封装

看很多react Keepalive的实现目前是还没找到什么方案,也皮厚的找过作者神说要有光,但是要是知道我是被人忽悠了,绝对不会去打扰大佬

tip:先自己敲,再问,不要让自己陷于蠢逼的尴尬

发现问题后,我去问日常开发Vue的童鞋们,因为我两年没写vue了,我说:你们是怎么实现tabs布局的,Vue的Keepalive是怎么实现只有显示哪个页面,别的组件存储而不运行watch的,然后说了一下我在react开发tabs的时候尴尬。

Vueer:vue不会有这个问题,自带的Keepalive封装的很完美,react没有这个功能吗?

就这样我信了半年,但是这个bug我说,我只能到这种程度了,要是说新项目还可以再useEffect再封装一层自定义的hooks,增加一点心智负担,让别人用封装的来,再useEffect内做判断是否是显示的path之类的。

这边年来反正这个功能做了一个开关,项目经理自己用,别人要是没问也不告诉别人有开发这东西,嘿嘿嘿

之所以又捞起来是别的几个项目也有人问,然后问我能不能迁移给别的项目,那他妈不得王炸,别的项目有的还是umi3没升级的。

2 vue3的Keepalive

先说结论吧:vue的Keepalive也能解决这个问题,纯属胡扯

实在想不到什么好的方案,闲余时间,就用vue写了一个demo,想看看vue是怎么实现的,因为react除了就是hidden,或者超出overflow 然后切换是平移像轮播图一样,实在想不出什么方案能保存原本的数据了。

image.png

<template>
  home
  <ul>
    <li v-for="item in router.getRoutes().filter(r=>r.path!=='/')" @click="liHandler(item)">
      {{ item.path }}
    </li>
  </ul>
</template>
<script lang="ts" setup>
import {useRouter} from "vue-router";

const router = useRouter()

const liHandler = (route) => {
  router.push({name: route.name, query: {path: route.path}})
}
</script>

简单来个demo的目录结构和代码,代码是会有问题的代码,不用细看...

两年没写vue还是遇到几个坑,先记录一下

2.1 vue3的routerview不能写在keepAlive内

// home.vue
<template>
  <!--  <RouterView v-slot="{Component}">-->
  <!--    <KeepAlive>-->
  <!--      <component :is="Component"/>-->
  <!--    </KeepAlive>-->
  <!--  </RouterView>-->

  <KeepAlive>
    <RouterView></RouterView>
  </KeepAlive>
</template>

image.png

注释掉的是正确的,这里不提一嘴,vue的console.log的体验感很ok,下面列的就是正确的,我还去百度了为啥Keepalive无效,直到截这张图写这文的时候才看到,人家都写了,哈哈哈哈

2.2 router.push地址不变

主要原因是路由创建的是 createMemoryHistory 这玩意不知道是啥 没用过,我是复制vueRoute官网demo的,一开始没注意,改成createWebHistory就好

import {createRouter, createWebHistory} from "vue-router";

const routes = [
    {path: "/", component: () => import("./components/Home.vue")},
    {path: "/aaa", component: () => import("./components/Aaa.vue"), name: 'aaa'},
    {path: "/bbb", component: () => import("./components/Bbb.vue"), name: 'bbb'},
    {path: "/ccc", component: () => import("./components/Ccc.vue"), name: 'ccc'},
    {path: "/ddd", component: () => import("./components/Ddd.vue"), name: 'ddd'},
]
const router = createRouter({
    history: createWebHistory(),
    routes,
})

export default router

2.3 router.push不拼接?传参

router.push(path, query: {path}})

一开始是这么写的,用path+query,这个问题纯粹弱智了,太久没写有点菜,改成name+query就行

2.4 vue Keepalive测试

我先点了去aaa,然后返回首页点ccc,这里可以看到,aaa页面的watch触发了!触发了!触发了!

image.png

然后去看了下源码,简约版如下

const KeepAliveImpl = {
  name: `KeepAlive`,

  // 私有属性 标记 该组件是一个KeepAlive组件
  __isKeepAlive: true,
  props: {
    // 用于匹配需要缓存的组件
    include: [String, RegExp, Array],
    // 用于匹配不需要缓存的组件
    exclude: [String, RegExp, Array],
    // 用于设置缓存上线
    max: [String, Number]
  },
  setup(props, { slots }) {
    // 省略部分代码...

    // 返回一个函数
    return () => {
      if (!slots.default) {
        return null
      }
      
      // 省略部分代码...
      
      // 获取子节点
      const children = slots.default()
      // 获取第一个子节点
      const rawVNode = children[0]
      // 返回原始Vnode
      return rawVNode
    }
  }
}

这不就是存下来了children,但是有个有意思的是

前面说过,react是通过缓存住组件,然后用hidden来控制展示哪个隐藏哪个,vue这边的dom渲染出来不是。

image.png

官网也说了动态组件是会卸载的

image.png

3 分析一波

image.png

直接用神说要有光的代码跑起来,这么一比可以看出来dom上的差异

然后把代码的显示改成判断语句渲染,测试一下

image.png

测试图:

image.png

image.png

然后去首页,再回到/bbb,发现数字又变回0了

先来看下React的渲染,通过卡颂大佬的文章,找到了react在render阶段

500行左右

image.png 这是判断是mountd还是update的一个标志,然而我们已经卸载了,这里肯定是null,那么就会去创建组建,仅仅是创建。

而vue因为自身有keepalive,在render阶段,是有对标志为keepalive的做patch的逻辑

image.png

image.png 所以keepalive的组件不会再走created和mounted,而是直接进行diff进行patch

总结

  • 公共参数无论是vue还是react都会被监听到
  • react想要实现像vue一样的效果只能等react官方适配

image.png 也是有提过啦