背景
如果您使用过Vue,那么您会知道它具有一个非常好的组件(keep-alive),它可以保持组件的鲜活状态以避免重复渲染。
有时,我们希望列表页面在进入详细信息页面后缓存页面状态。当详细信息页面返回到列表页面时,列表页面仍与切换之前相同,这在管理系统中是一个非常常见且较为重要的需求。
但是在React这实际上很难实现,因为一旦卸载,React中的组件就无法重用。在问题#12039中提出了两种解决方案。通过使用样式切换组件显示(display: none | block;),这可能会引起问题,例如在切换组件时,您将无法使用动画。或使用Mobx和Redux等数据流管理工具,但这太麻烦了。
当然曾经也有人提出新的方案,无法把持鲜活状态的原因是因为在生命周期中执行了卸载过程,如果我们对路由或者react 进行修改,让其不执行卸载过程是否可行?这个方案也被官方否决了
解决方案
我们希望:
- 简易的api 使用
- 对react 或者 react-router 尽量不要有侵入
- 支持 对redux mobx 等数据管理库的支持
神说要有光,于是便有了光
react-activation 便应运而生。
原理
实现原理说起来较为简单,核心代码仅为70行。由于React会卸载掉处于固有组件层级内的组件,所以我们需要将中的组件,也就是其children属性抽取出来,渲染到一个不会被卸载的组件内,再使用DOM操作将内的真实内容移入对应,就可以实现此功能。
本质上就是利用利用React.context 来缓存children,当cache key 匹配上时再将对应的children 挂载到对应的父节点上。
实际实现过程中,遇到了许多问题,都是由于打破了原有 React 层级关系引起的,例如
渲染延迟(react-activation 中已修复)
Context 上下文功能失效(react-activation 中已修复)
Error Boundaries 失效(react-activation 中已修复)
React.Suspense & React.lazy 失效(react-activation 中已修复)
React 合成事件冒泡失效
其他未发现的功能
但上述问题,大多数是可以通过桥接机制修复的,具体可以参考此处 issues;
效果展示
实践
缓存组件是拿空间换时间,随着缓存的东西越来越多,内存的占用也将是巨大的直到最后页面崩溃。因此我们最好采用有条件的缓存且可手动进行销毁。方案有2个:
-
多tabs方案,我们将所有页面进行缓存,每当打开一个页面那么就在tabs中+1,并为其增加删除按键,可以进行手动销毁
-
点击其他的菜单按钮进行缓存销毁。我个人认为我们缓存的仅为我们所关注的页面,例如我在操作页面a 的时候,那么想看的也少页面a 的详情页。当我进行菜单跳转的时候,即为不在关注当前,那么就执行清空缓存事件,将缓存的页面全部清理掉,这样的好处是让用户无感知,简易了开发流程。
致谢@CJY0208的帮助。