前情提要
需求背景:实现多页签
目的就是让用户在路由之间切换,页面无需重新渲染,保存页面内dom的状态、表单的数据、甚至可以保存滚动的位置,让操作者无感切换单页页面。
有种SPA里塞了MPA的感觉 :)
解决方案探索
项目现状:写法属于常规写法,现在他没法缓存:(
<Switch>
{routeArr.map(r => { // 一些逻辑
return (
<Route exact key={key} path={path} render={() => {...}} /> )
}}
</Switch>
我们知道,react-router其实是在history库的基础上实现了路径和组件的同步,在路由前进后退时路径不匹配就会卸载对应组件,当然这是很自然的SPA设计,本就应该如此。
但如何实现页面缓存呢?
需求时间比较紧,没有时间造页面缓存的轮子,刚入手其实有几种思路:
首先我想到了或许可以使用redux,缓存页面的数据,但我们是中期提的需求,入侵改造量貌似太大了,pass
听说antd-pro v6实现了这个功能,但我不想为了多页签应该功能绑定umi,pass
搜了一下github还是有不少现成的,但考虑到安全和维护性,最后选定了react-router-cache-route,(github.com/CJY0208/rea… )star 1k+,接入文档写的也相对清楚,issue一条条看下来作者也会定期更新,好,就是你了!
接入和踩坑
首先,在Router.tsx里面, 把可能用到的都引入了
import CacheRoute, {
CacheSwitch,
clearCache,
dropByCacheKey,
getCachingKeys,
useDidCache,
useDidRecover,
} from "react-router-cache-route"
// 放到控制台也能操作
window.dropByCacheKey = dropByCacheKey
window.clearCache = clearCache
window.getCachingKeys = getCachingKeys
然后把Route都改成CacheRoute,把Switch都改成CacheSwitch
看起来是这样的结构
<CacheSwitch>
<CacheRoute />
</CacheSwitch>
当然想要实现缓存需要加几个参数给CacheRoute,在我的项目里可能也就如下几个参数比较有用了
- when :几时缓存,默认是forward,我也认为只让前进缓存比较合理,一般用history.push 或replace就能实现缓存了
- saveScrollPosition: 在tob场景里面滑动表格回来的时候还能回到之前的位置
- cachekey: 这个我认为是此页面的唯一标识,用于后面tab的删除,你可以设置此页面的唯一值,比如路径
- autoFreeze:安装最新版v13.0的我在文档没有标注这个参数的情况下使用时,发现表单能聚焦,但都没法输入,找了很久的原因,在release里面找到了版本13的changelog, 这个参数默认是true的,属实有点坑了!设置false就能解决这个问题
还有几个参数慎用
- multiple: 设置了貌似没什么作用,上面的参数够用了,项目带参数跳转的不需要缓存
- unmount: 设置了貌似没什么作用, 据说是缓存后会卸载真实dom
目前为止,可能看不到效果, 毕竟我们还是spa状态
我们在CacheRoute里要render的组件里面加上Tabs
!!!目前的实现只能删除当前页的tab,可能后续有时间再优化解决一下
这是主要实现逻辑:
// 当前的缓存的key
const [currentKeys, setCurrentKeys] = useState<string[]>([])
// 目前在项目里面我在useeffect和恢复时都设置keys
useDidRecover(() => {
setCurrentKeys(window.getCachingKeys())
}, [])
useEffect(() => {
setCurrentKeys(window.getCachingKeys())
}, [])
// 删除逻辑
const remove = (
key: React.MouseEvent | React.KeyboardEvent | string,
action: "add" | "remove",
) => {
if (action === "remove") {
window.dropByCacheKey(key as string)
if (currentKeys.length > 1) {
history.push(
currentKeys.filter((i) => i !== key)[currentKeys.length - 2],
)
} else {
history.push("/")
}
}
}
// 多页签的tabs组件 放在CacheRoute里面的render内
<Tabs
hideAdd
type="editable-card"
// 我用pathname作为key
activeKey={location.pathname}
items={
currentKeys?.map?.((item) => ({
key: item,
label: routeMap[item] || item,
closable: location.pathname === item,
})) || []
}
onChange={(key) => {
history.replace(key)
}}
onEdit={remove}
/>
到这多页签基本功能就大功告成了
你现在能看到tab切换页面,立刻展示了当前页,甚至记住了浏览位置,说明缓存生效了!
总结
其实还是有不少地方的实现不够完善的地方的,后面会继续优化!