如何实现 React 中的状态自动保存?

什么是状态保存?

假设有下述场景:

移动端中,用户访问了一个列表页,上拉浏览列表页的过程中,随着滚动高度逐渐增加,数据也将采用触底分页加载的形式逐步增加,列表页浏览到某个位置,用户看到了感兴趣的项目,点击查看其详情,进入详情页,从详情页退回列表页时,需要停留在离开列表页时的浏览位置上

类似的数据或场景还有已填写但未提交的表单管理系统中可切换和可关闭的功能标签等,这类数据随着用户交互逐渐变化或增长,这里理解为状态,在交互过程中,因为某些原因需要临时离开交互场景,则需要对状态进行保存

在 React 中,我们通常会使用路由去管理不同的页面,而在切换页面时,路由将会卸载掉未匹配的页面组件,所以上述列表页例子中,当用户从详情页退回列表页时,会回到列表页顶部,因为列表页组件被路由卸载后重建了,状态被丢失

如何实现 React 中的状态保存

在 Vue 中,我们可以非常便捷地通过 <keep-alive>[1] 标签实现状态的保存,该标签会缓存不活动的组件实例,而不是销毁它们

而在 React 中并没有这个功能,曾经有人在官方提过功能 issues[2] ,但官方认为这个功能容易造成内存泄露,表示暂时不考虑支持,所以我们需要自己想办法了

常见的解决方式:手动保存状态

手动保存状态,是比较常见的解决方式,可以配合 React 组件的 componentWillUnmount 生命周期通过 redux 之类的状态管理层对数据进行保存,通过 componentDidMount 周期进行数据恢复

在需要保存的状态较少时,这种方式可以比较快地实现我们所需功能,但在数据量大或者情况多变时,手动保存状态就会变成一件麻烦事了

作为程序员,当然是尽可能懒啦,为了不需要每次都关心如何对数据进行保存恢复,我们需要研究如何自动保存状态

通过路由实现自动状态保存(通常使用 react-router[3]

既然 React 中状态的丢失是由于路由切换时卸载了组件引起的,那可以尝试从路由机制上去入手,改变路由对组件的渲染行为

我们有以下的方式去实现这个功能

1.

重写 <Route> 组件,可参考 react-live-route[4]

重写可以实现我们想要的功能,但成本也比较高,需要注意对原始 <Route> 功能的保存,以及多个 react-router 版本的兼容

2.

替换路由库为 react-keeper[5]

完全替换掉路由方案是一个风险较大的事情,需要较为慎重地考虑

3.

基于 <Route> 组件现有行为做拓展,可参考 react-router-cache-route[6]

在阅读了 <Route> 的源码后发现,如果使用 component 或者 render 属性,都无法避免路由在不匹配时被卸载掉的命运

但将 children 属性当作方法来使用,我们就有手动控制渲染的行为的可能,关键代码在此处 https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Route.js#L41-L72

   // 节选自 Route 组件中的 render 函数   if (typeof children === "function") {     children = children(props); // children 是函数时,将对 children 进行调用得到真实的渲染结果     if (children === undefined) {       ...       children = null;     }   }   return (     <RouterContext.Provider value={props}>       {children && !isEmptyChildren(children) // children 存在时,将使用 children 进行渲染         ? children         : props.match           ? component             ? React.createElement(component, props)             : render               ? render(props)               : null // 使用 render 属性无法阻止组件的卸载           : null // 使用 component 属性无法阻止组件的卸载       }     </RouterContext.Provider>   );

基于上述源码探究,我们可以对 <Route> 进行拓展,将 <Route> 的不匹配行为由卸载调整为隐藏,如下

 <Route exact path="/list">     {props => (         <div style={props.match ? null : { display: ‘none‘ }}>             <List {...props} />         </div>     )} </Route>

上述是最简的调整方式,实际情况中也需要考虑隐藏状态下 match 为 null 导致组件报错的问题,且由于不再是组件卸载,所以和 TransitionGroup 配合得不好,导致转场动画难以实现

使用 react-router-cache-route[7],得到的效果大致如下图,

上述探究了通过路由入手实现自动状态保存的可能,以及现有的实现,但终究不是真实的、纯粹的 KeepAlive 功能,接下来我们尝试探究真实 KeepAlive 功能的实现

模拟真实的 <KeepAlive> 功能

以下是期望的使用方式

function App() {  const [show, setShow] = useState(true)  return (    <div>      <button onClick={() => setShow(show => !show)}>Toggle</button>      {show && (        <KeepAlive>          <Test />        </KeepAlive>      )}    </div>  )}

实现原理说起来较为简单,由于 React 会卸载掉处于固有组件层级内的组件,所以我们需要将 <KeepAlive> 中的组件,也就是其 children 属性抽取出来,渲染到一个不会被卸载的组件内,就可以实现此功能

以下是 react-activation[8] 的实现效果

在线示例[9]

实际实现过程中,遇到了许多问题,都是由于打破了原有 React 层级关系引起的,例如

•渲染延迟

•Provider 上下文功能失效•Error Boundaries 失效•React.Suspense & React.lazy 失效•React 合成事件冒泡失效•其他未发现的功能

但上述问题,大多数是可以通过桥接机制修复的

相同的、更早的实现还有 react-keep-alive[10]

结语

状态缓存是应用中十分常见的需求,在需要处理的数据量较少时,使用手动状态缓存就可以解决大多数问题,但当情况复杂时,还需要尝试将缓存功能单独拎出来解决,以便在业务开发过程中更好地进行关注点分离

目前的实现都有各自的问题,但其探究过程十分有趣,最好的方式仍是官方的支持,但目前还不能报太大期望

References

[1] <keep-alive>https://cn.vuejs.org/v2/api/#keep-alive
[2] issues: https://github.com/facebook/react/issues/12039
[3] react-router: https://reacttraining.com/react-router/
[4] react-live-route: https://github.com/fi3ework/react-live-route
[5] react-keeper: https://github.com/vifird/react-keeper
[6] react-router-cache-route: https://github.com/CJY0208/react-router-cache-route/blob/master/README_CN.md
[7] react-router-cache-route: https://github.com/CJY0208/react-router-cache-route/blob/master/README_CN.md
[8] react-activation: https://github.com/CJY0208/react-activation/blob/master/README_CN.md
[9] 在线示例: https://codesandbox.io/s/affectionate-beaver-solkt
[10] react-keep-alive: https://github.com/StructureBuilder/react-keep-alive

原文地址:https://www.cnblogs.com/duanlibo/p/11634562.html

时间: 2024-11-01 06:38:13

如何实现 React 中的状态自动保存?的相关文章

Android开发中Activity状态的保存与恢复

当置于后台的Activity因内存紧张被系统自动回收的时候,再次启动它的话他会重新调用onCretae()从而丢失了之前置于后台前的状态. 这时候就要重写Activity的两个方法来保存和恢复状态,具体用途举个例子:你正在编辑短信,这时候来了一个电话,打完电话回到短信界面, 短信刚好被系统回收重启,这时原先编辑了一半的内容总不能丢失了吧,这样影响用户体验.所以解决办法如下: 1 private static final String INSTANCE_STATUS="instance_statu

http协议无状态中的 &quot;状态&quot; 到底指的是什么?!

引子: 最近在好好了解http,发现对介绍http的第一句话[http协议是无状态的,无连接的]就无法理解:无状态的[状态]到底指的是什么?! 找了很多资料不仅没有发现有一针见血正面回答这个问题的,而且有些解释还充斥了各种错误,看着看着就觉得心里憋着一股浊气吐不出来 于是在看了很多资料之后,我一口吐出浊气,大声正面提出这个问题:http协议无状态中的[状态]到底指的是什么?! 然后开始不断探索解决这个问题... 最终很高兴的是我找到了让人满意的答案,先卖个关子,各位如果着急可以直接拉到最下查看

react中redux的理解

定义 redux可以看作是flux的进阶版,主要用于react中公共状态(数据)的管理 redux底层原理 redux有一个createStore方法,这个方法用户创建公共存储空间,createStore方法接收一个纯函数作为作为参数,在纯函数中处理数据并返回处理后的数据.当createStore方法执行完成后会返回一个store对象,这个对象内提供一些方法,组件中通过调用store的这些方法去获取或者修改公共存储空间内的数据. 这里说store的几个方法:dispatch用于发送action;

React 中的 Component、PureComponent、无状态组件 之间的比较

React 中的 Component.PureComponent.无状态组件之间的比较 table th:first-of-type { width: 150px } 组件类型 说明 React.createClass 不使用ES6语法,只能使用 React.createClass 来创建组件:React对属性中的所有函数都进行了this绑定 Component 使用ES6语法创建组件:React并没有对内部的函数,进行this绑定 PureComponent shouldComponentUp

教你如何在ZBrush中自动保存

在使用 ZBrush执行任何会话期间,您都可以设置将文件自动保存,并可以修改保存时间间隔,文件保存位置等设置.发生系统错误后要重新启动ZBrush®时,可以从临时文件夹或指定的文件夹中恢复备份文件.如果您选择不恢复备份文件,退出应用程序后文件会自动擦除. 首先在“Preferences”菜单的最后倒数第四个,找到“QuickSave”选项,并单击展开它. “QuickSave”选项中各参数按钮的功能如下: Maximum Duration(最长时间):每隔多长时间保存一次,单位是分钟. Rest

怎样关掉EDIUS 8中的自动保存?

EDIUS软件的版本从6到现在的8,都开始默认每间隔3分钟自动保存一次所做的工程文件,有的小伙伴可能会觉得有些烦扰,想要关掉EDIUS自动保存设置,要怎么做呢?下面,看小编给你们解决烦恼. 若有疑问可直接访问:http://www.ediuschina.com/changjian/edius-guan-zidongbaocun.html 1.点击菜单栏中的设置,选择“用户设置”,打开“用户设置”对话框,从“应用”选项卡中找到“工程文件”,在下面一点可以看到“自动保存”的一系列选项,将目标下的“工

intellig idea中jsp或html数据没有自动保存和更换字体

主题一:保存数据jsp intellig idea是自动保存数据的,看到没有保存 解决方案: 成功解决 主题二:更换字体: 或者快捷键Ctel+Alt+s 成功解决 原文地址:https://www.cnblogs.com/weibanggang/p/9398498.html

React中Redux的有关知识

Redux的设计思想很简单,就两句话. * Web应用是一个状态机,视图与状态是一一对应的* 所有的状态,保存在一个对象里面 Store: Store就是保存数据的地方,你可以把它看成一个容器.整个应用只能有一个Store. Redux提供createStore这个函数,用来生成Store. import { createStore } from 'redux'; const store = createStore(fn); 上面代码中,createStore函数接受里呢一个函数作为参数,返回新

React中setState同步更新策略

本文和大家分享的主要是React中setState同步更新相关内容,希望对大家学习React有所帮助. 为了提高性能React将setState设置为批次更新,即是异步操作函数,并不能以顺序控制流的方式设置某些事件,我们也不能依赖于 this.state 来计算未来状态.典型的譬如我们希望在从服务端抓取数据并且渲染到界面之后,再隐藏加载进度条或者外部加载提示: componentDidMount() { fetch('https://example.com') .then((res) => re