错怪react的半年-聊聊keepalive
背景
在半年前的某一天,一个运行一年的项目被人提了建议,希望有一个tabs页面,因为他需要多个页面找东西。
我:滚,自己开浏览器标签页(此处吹牛逼)
项目经理:我审核的时候也需要看别人提交的数据是否正确,我也需要看,很多人提了建议
我:作为一个合格的外包,肯定以项目经理体验为主(狗头保命)
1 React的Keepalive
简单实现React KeepAlive不依赖第三方库(附源码)React KeepAlive 实现,不依赖第三方库,支持 - 掘金
神说要有光《React通关秘籍》
....还有一些没有收藏的keepalive实现
代码不想多赘述,我找了很多资料基本思路讲一下
基本是通过react-router来实现
- 弄一个map来存储{path:<OutLet|useOutlet>}
- 然后根据当前路由path来决定哪些组件需要渲染,不渲染的hidden
- 然后在最外层布局套一层Tabs布局用react的context来传递
出现的问题是:
就是当依赖项是一个公共数据的时候,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 然后切换是平移像轮播图一样,实在想不出什么方案能保存原本的数据了。
<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>
注释掉的是正确的,这里不提一嘴,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触发了!触发了!触发了!
然后去看了下源码,简约版如下
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渲染出来不是。
官网也说了动态组件是会卸载的
3 分析一波
直接用光哥的代码跑起来,这么一比可以看出来dom上的差异
然后把代码的显示改成判断语句渲染,测试一下
测试图:
然后去首页,再回到/bbb,发现数字又变回0了
先来看下React的渲染,通过卡颂大佬的文章,找到了react在render阶段
这是判断是mountd还是update的一个标志,然而我们已经卸载了,这里肯定是null,那么就会去创建组建,仅仅是创建。
而vue因为自身有keepalive,在render阶段,是有对标志为keepalive的做patch的逻辑
所以keepalive的组件不会再走created和mounted,而是直接进行diff进行parch
总结
- 公共参数无论是vue还是react都会被监听到
- react想要实现像vue一样的效果只能等react官方适配
也是有提过啦
来源:juejin.cn/post/7436955628263784475