当前位置:   article > 正文

react 中的 keep-alive_react-keep-alive

react-keep-alive

需求场景

为了优化用户的体验,可能会遇到这样的需求:返回列表的时候,需要保持状态和滚动位置;或是页面内切换组件(比如切换 Tab )的时候,需要保持状态。

如果使用 Vue,就可以用 <keep-alive> 组件来让其包含的组件保留状态,实现组件缓存。但是在 React 中并没有这样的功能,而且在这个 issues 中也可以看到,官方认为 <keep-alive> 容易造成内存的泄露,因此不准备引入这样的 API,但也许未来会提供更好的缓存方式,所以目前还需通过其他方法实现这类需求。

方法对比

  • redux 或 localStorage/sessionStorage
    通过这几种方式把状态存储起来,再次回到页面时再把状态取出来使用,实现缓存的效果。
    这种方式在数据量较少时可以很好地实现缓存,但在状态多或情况多变的时候,就会让改动较大、控制起来也比较复杂,不是一个通用的方法。
  • 第三方库
    • react-keep-alive
      可以实现缓存的效果,但是会造成数据驱动失效。
      虽然可以缓存最后一次状态渲染结果,但是后面数据变化无法再进行数据驱动。
    • react-activation
      可以实现缓存的效果,较推荐。
    • umi-plugin-keep-alive
      是基于 react-activation 的 umi 插件。

react-activation 的使用

其实使用方法很简单,用 <KeepAlive> 包裹需要进行缓存的组件,并在一个不会卸载的父组件内包裹上 <AliveScope> 即可。

react-activation 分别为类组件和函数组件提供了生命周期 componentDidActivate、componentWillUnactivate 或 hooks useActivate、useUnactivate,来对应恢复缓存和进行缓存两种状态,以及是否保存滚动位置、手动控制缓存等等功能。

通过 作者的最简实现 可以看到组件缓存的效果和使用的示例,以及 react-activation 的最简实现方式,可以分析一下它的实现思路。

react-activation 的源码原理

最简实现:

import React, { Component, createContext } from 'react'

const { Provider, Consumer } = createContext()
const withScope = WrappedComponent => props => (
  <Consumer>{keep => <WrappedComponent {...props} keep={keep} />}</Consumer>
)

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>
    )
  }
}

@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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

实现的过程大致是,由不会被卸载的 AliveScope 组件通过上下文,把一个 keep 方法传递出去。

然后一个高阶组件获取到 keep 方法,并把 children 属性传入 KeepAlive 组件(这也是 react-activation 实现了数据驱动,而 react-keep-alive 数据驱动失效,两个库的主要区别原因 )。

在 KeepAlive 组件中调用 keep 方法,把 children 属性缓存到 AliveScope 的 state 中。

在 state 更新后,把 ref (真实 DOM)返回给 KeepAlive 组件。KeepAlive 组件拿到真实 DOM 后,把它移动到自己组件内的某个占位中。

在 KeepAlive 组件卸载的以后,如果还需要重新加载,还可以从 AliveScope 组件中获取到缓存的虚拟 DOM 信息。
在这里插入图片描述
这个其实是伪造组件的思路,把 children 包裹起来并且传递出去,在缓存组件内被渲染,当前组件正常地更新卸载。

当前组件卸载的时候,children 也被卸载了,但是它的虚拟 DOM 已经被缓存在了缓存组件中。

这个组件重新被加载的时候,把缓存直接渲染后移入当前组件,就恢复了组件卸载前状态。

不过这个库也因为破坏了原来的渲染层级,遇到了一些已修复和还未修复的问题,还是希望官方可以支持实现这个功能吧。

参考

React 中的状态自动保存(KeepAlive)—— react-activation 作者
在React中实现和Vue一样舒适的keep-alive

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/119130
推荐阅读
相关标签
  

闽ICP备14008679号