前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在React中实现和Vue一样舒适的keep-alive

在React中实现和Vue一样舒适的keep-alive

作者头像
Peter谭金杰
发布2020-07-20 10:17:30
2.2K0
发布2020-07-20 10:17:30
举报

正式开始

  • 什么是状态保存?
  • 假设有下述场景:
  • 移动端中,用户访问了一个列表页,上拉浏览列表页的过程中,随着滚动高度逐渐增加,数据也将采用触底分页加载的形式逐步增加,列表页浏览到某个位置,用户看到了感兴趣的项目,点击查看其详情,进入详情页,从详情页退回列表页时,需要停留在离开列表页时的浏览位置上
  • 类似的数据或场景还有已填写但未提交的表单、管理系统中可切换和可关闭的功能标签等,这类数据随着用户交互逐渐变化或增长,这里理解为状态,在交互过程中,因为某些原因需要临时离开交互场景,则需要对状态进行保存
  • 在 React 中,我们通常会使用路由去管理不同的页面,而在切换页面时,路由将会卸载掉未匹配的页面组件,所以上述列表页例子中,当用户从详情页退回列表页时,会回到列表页顶部,因为列表页组件被路由卸载后重建了,状态被丢失
如何实现 React 中的状态保存
  • 在 Vue 中,我们可以非常便捷地通过<keep-alive>标签实现状态的保存,该标签会缓存不活动的组件实例,而不是销毁它们
  • 而在 React 中并没有这个功能,曾经有人在官方提过功能 issues ,但官方认为这个功能容易造成内存泄露,表示暂时不考虑支持,所以我们需要自己想办法了
  • 常见的解决方式:手动保存状态
  • 手动保存状态,是比较常见的解决方式,可以配合 React 组件的 componentWillUnmount 生命周期通过 redux 之类的状态管理层对数据进行保存,通过 componentDidMount 周期进行数据恢复
  • 在需要保存的状态较少时,这种方式可以比较快地实现我们所需功能,但在数据量大或者情况多变时,手动保存状态就会变成一件麻烦事了
  • 作为程序员,当然是尽可能懒啦,为了不需要每次都关心如何对数据进行保存恢复,我们需要研究如何自动保存状态
  • 最初的版本react-keep-alive
  • 1500行TypeScript代码在React中实现组件keep-alive 我的这篇文章对源码进行了解析,但是这个库存在断层现象,虽然可以缓存最后一次状态渲染结果,但是后面数据变化无法再进行数据驱动。而且是借助React.createPortal 借助实现,我跟下面这个库的作者都觉得这是多余的,其实只需要抽取children属性,再封装一次HOC高阶组件即可。
  • 总体来说,react-keep-alive这个库比较重,实现原理也不难,就是笨重,断层,源码跳来跳去,真的理清楚了就好
react-activation优雅的实现
  • 效果实现:

庖丁解牛,源码解析
  • 最简单版本的react中keep-alive实现演示地址
  • 使用方式:开箱即用
代码语言:javascript
复制
import React, { useState } from 'react'  
import { render } from 'react-dom'  
import KeepAlive, { AliveScope } from './KeepAlive'  
  
  
  
function App() {  
  const [show, setShow] = useState(true)  
  return (  
    <div>  
      <button onClick={() => setShow(show => !show)}>Toggle</button>  
      <p>无 KeepAlive</p>  
      {show && <Counter />}  
      <p>有 KeepAlive</p>  
      {show && (  
        <KeepAlive id="Test">  
          <Counter />  
        </KeepAlive>  
      )}  
    </div>  
  )  
}  
  
....  
  
  
render(  
  <AliveScope>  
    <App />  
  </AliveScope>,  
  document.getElementById('root')  
)  
  • 注意 :缓存的虚拟DOM元素会储存在AliveScope 组件中,所以它不能被卸载
  • 使用AliveScope 配合KeepAlive即可达到缓存效果,类似react-keep-alive
  • 首先我们看看AliveScope 组件做了什么事情
代码语言:javascript
复制
export class AliveScope extends Component {  
  nodes = {}  
  state = {}  
  
  keep = (id, children) =>  
    new Promise(resolve =>  
      this.setState(  
        {  
          [id]: { id, children }  
        },  
        () => resolve(this.nodes[id])  
      )  
    )  
  
  render() {  
    return (  
      <Provider value={this.keep}>  
        {this.props.children}  
        {Object.values(this.state).map(({ id, children }) => (  
          <div  
            key={id}  
            ref={node => {  
              this.nodes[id] = node  
            }}  
          >  
            {children}  
          </div>  
        ))}  
      </Provider>  
    )  
  }  
}  
  • 它的源码只有几十行,很简单,这里的this.props.children是虚拟DOM,经过Babel编译和React处理,最终会转化成真实DOM节点渲染
  • 逐步解析:
代码语言:javascript
复制
{this.props.children}  
  • 是这个组件的所有子元素,必须要渲染
  • 使用React的Context API进行传递KEEP方法给所有的子孙组件,每次这个方法被调用,都会造成AliveScope 组件重新渲染,进而刷新子组件,并且返回一个真实的DOM节点,这个真实的DOM节点就可以被直接DOM操作。
  • 这张思维导图,可以很清楚的表示,我们的缓存实现方式,如果看不懂,慢慢往下看
KeepAlive组件的源码
代码语言:javascript
复制
import React, { Component, createContext } from 'react'  
  
const { Provider, Consumer } = createContext()  
  
const withScope = WrappedCompoennt => props => (  
  <Consumer>{keep => <WrappedCompoennt {...props} keep={keep} />}</Consumer>  
)  
  
  
  
@withScope  
class KeepAlive extends Component {  
  constructor(props) {  
    super(props)  
    this.init(props)  
  }  
  
  init = async ({ id, children, keep }) => {  
    const realContent = await keep(id, children)  
    this.placeholder.appendChild(realContent)  
  }  
  
  render() {  
    return (  
      <div  
        ref={node => {  
          this.placeholder = node  
        }}  
      />  
    )  
  }  
}  
  
export default KeepAlive  
  • withScope是一个高阶组件,将KeepAlive组件传入,返回一个新的组件,这里使用了装饰器,@withScope.其实最终export default 的是withScope(KeepAlive)
  • 这里就是跟react-keep-alive的真正区别,withScope使用了context api捕获了传入的虚拟DOM节点,桥接了父组件以及KeepAlive组件的关联,一旦children属性改变,那么withScope被刷新,进而传入新的children属性给KeepAlive组件,导致数据驱动可以进行组件刷新
  • 这又印证了那句话
  • 在计算机的世界里,如果出现解决不了的问题,那就加一个中间层,如果还不行就加两个 --来自不知名码农Peter
  • 这里按照代码运行逻辑,完整的解析了它的简单缓存机制实现,思路整体比较清晰,加上代码自己断点调试难度应该比较低,个人觉得这个库的设计和思想,都是不错的,值得推广,作者也是比较乐意解答问题。大家有问题可以在github上提问。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正式开始
    • 如何实现 React 中的状态保存
      • react-activation优雅的实现
        • 庖丁解牛,源码解析
          • KeepAlive组件的源码
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档