基于react-router-cache-route实现多页签的接入和踩坑小记

901 阅读3分钟

前情提要

需求背景:实现多页签

目的就是让用户在路由之间切换,页面无需重新渲染,保存页面内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,在我的项目里可能也就如下几个参数比较有用了

  1. when :几时缓存,默认是forward,我也认为只让前进缓存比较合理,一般用history.push 或replace就能实现缓存了
  2. saveScrollPosition: 在tob场景里面滑动表格回来的时候还能回到之前的位置
  3. cachekey: 这个我认为是此页面的唯一标识,用于后面tab的删除,你可以设置此页面的唯一值,比如路径
  4. autoFreeze:安装最新版v13.0的我在文档没有标注这个参数的情况下使用时,发现表单能聚焦,但都没法输入,找了很久的原因,在release里面找到了版本13的changelog, 这个参数默认是true的,属实有点坑了!设置false就能解决这个问题

还有几个参数慎用

  1. multiple: 设置了貌似没什么作用,上面的参数够用了,项目带参数跳转的不需要缓存
  2. 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切换页面,立刻展示了当前页,甚至记住了浏览位置,说明缓存生效了!

总结

其实还是有不少地方的实现不够完善的地方的,后面会继续优化!