Have ideas to improve npm?Join in the discussion! »

    react-keep-active

    1.0.0 • Public • Published

    React Activation

    Vue 中 <keep-alive /> 功能在 React 中的实现


    定位与 react-keep-alive 相同

    但修复了 https://github.com/StructureBuilder/react-keep-alive/issues/36 中的部分问题

    配合 babel 预编译实现更稳定的 KeepAlive 功能

    在线 Demo


    兼容性

    • React v16+

    • 兼容 SSR


    安装

    yarn add react-activation
    # or 
    npm install react-activation

    使用方式

    .babelrc 中增加 react-activation/babel 插件

    该插件会于编译阶段在各 JSX 元素上增加 _ka 属性,帮助 KeepAlive 运行时按渲染位置生成唯一的缓存 id 标识

    {
      "plugins": [
        "react-activation/babel"
      ]
    }

    业务代码中

    import React, { Component, useState } from 'react'
    import ReactDOM from 'react-dom'
    import KeepAlive, { AliveScope, withActivation } from 'react-keep-active'
     
    @withActivation
    class Test extends Component {
      state = {
        count: 0
      }
     
      setCount = count => this.setState({ count })
     
      componentDidActivate() {
        console.log('Test: componentDidActivate')
      }
     
      componentWillUnactivate() {
        console.log('Test: componentWillUnactivate')
      }
     
      render() {
        const { count } = this.state
        
        return (
          <div>
            count: {count}
            <button onClick={() => this.setCount(count + 1)}>add</button>
          </div>
        )
      }
    }
     
    function App() {
      const [show, setShow] = useState(true)
     
      return (
        <AliveScope>
          <button onClick={() => setShow(show => !show)}>Toggle</button>
          {show && (
            <KeepAlive>
              <Test />
            </KeepAlive>
          )}
        </AliveScope>
      )
    }
     
    ReactDOM.render(<App />, document.getElementById('root'))

    生命周期

    ClassComponent 可配合 withActivation 装饰器

    使用 componentDidActivatecomponentWillUnactivate 对应激活与缓存两种状态

    FunctionComponent 则分别使用 useActivateuseUnactivate hooks 钩子

    ...
    import KeepAlive, { useActivate, useUnactivate, withActivation } from 'react-keep-active'
     
    @withActivation
    class TestClass extends Component {
      ...
      componentDidActivate() {
        console.log('TestClass: componentDidActivate')
      }
     
      componentWillUnactivate() {
        console.log('TestClass: componentWillUnactivate')
      }
      ...
    }
    ...
    function TestFunction() {
      useActivate(() => {
        console.log('TestFunction: didActivate')
      })
     
      useUnactivate(() => {
        console.log('TestFunction: willUnactivate')
      })
      ...
    }
    ...
    function App() {
      ...
      return (
        {show && (
          <KeepAlive>
            <TestClass />
            <TestFunction />
          </KeepAlive>
        )}
      )
    }
    ...

    缓存控制

    自动控制缓存

    给需要控制缓存的 <KeepAlive /> 标签增加 when 属性,取值如下

    when 类型为 Boolean

    • true: 卸载时缓存
    • false: 卸载时不缓存
    <KeepAlive when={true}>

    when 类型为 Array

    第 1 位参数表示是否需要在卸载时缓存

    第 2 位参数表示是否卸载 <KeepAlive> 的所有缓存内容,包括 <KeepAlive> 中嵌套的所有 <KeepAlive>

    // 例如:以下表示卸载时不缓存,并卸载掉嵌套的所有 `<KeepAlive>`
    <KeepAlive when={[false, true]}>
      ...
        <KeepAlive>
          ...
            <KeepAlive>
              ...
            </KeepAlive>
          ...
        </KeepAlive>
      ...
    </KeepAlive>

    when 类型为 Function

    返回值为上述 BooleanArray,依照上述说明生效

    手动控制缓存

    1. 给需要控制缓存的 <KeepAlive /> 标签增加 name 属性

    2. 使用 withAliveScopeuseAliveController 获取控制函数

      • drop(name):

        按 name 卸载缓存状态下的 <KeepAlive> 节点,name 可选类型为 StringRegExp,注意,仅卸载命中 <KeepAlive> 的第一层内容,不会卸载 <KeepAlive> 中嵌套的、未命中的 <KeepAlive>

      • dropScope(name)

        按 name 卸载缓存状态下的 <KeepAlive> 节点,name 可选类型为 StringRegExp,将卸载命中 <KeepAlive> 的所有内容,包括 <KeepAlive> 中嵌套的所有 <KeepAlive>

      • clear()

        将清空所有缓存中的 KeepAlive

      • getCachingNodes()

        获取所有缓存中的节点

    ...
    import KeepAlive, { withAliveScope, useAliveController } from 'react-activation'
    ...
    <KeepAlive name="Test">
      ...
        <KeepAlive>
          ...
            <KeepAlive>
              ...
            </KeepAlive>
          ...
        </KeepAlive>
      ...
    </KeepAlive>
    ...
    function App() {
      const { drop, dropScope, clear, getCachingNodes } = useAliveController()
     
      useEffect(() => {
        drop('Test')
        // or
        drop(/Test/)
        // or
        dropScope('Test')
     
        clear()
      })
     
      return (
        ...
      )
    }
    // or
    @withAliveScope
    class App extends Component {
      render() {
        const { drop, dropScope, clear, getCachingNodes } = this.props
     
        return (
          ...
        )
      }
    }
    ...

    原理概述

    <KeepAlive />children 属性传递到 <AliveScope /> 中,通过 <Keeper /> 进行渲染

    <Keeper /> 完成渲染后通过 DOM 操作,将内容转移到 <KeepAlive />

    由于 <Keeper /> 不会被卸载,故能实现缓存功能


    Breaking Change 由实现原理引发的额外问题

    1. <KeepAlive /> 中需要有一个将 children 传递到 <AliveScope /> 的动作,故真实内容的渲染会相较于正常情况慢一拍

      将会对严格依赖生命周期顺序的功能造成一定影响,例如 componentDidMount 中 ref 的取值,如下

      class Test extends Component {
        componentDidMount() {
          console.log(this.outside) // will log <div /> instance
          console.log(this.inside) // will log undefined
        }
       
        render() {
          return (
            <div>
              <div ref={ref => {
                this.outside = ref
              }}>
                Outside KeepAlive
              </div>
              <KeepAlive>
                <div ref={ref => {
                  this.inside = ref
                }}>
                  Inside KeepAlive
                </div>
              </KeepAlive>
            </div>
          )
        }
      }

      ClassComponent 中上述错误可通过利用 withActivation 高阶组件修复

      FunctionComponent 目前暂无处理方式,可使用 setTimeoutnextTick 延时获取 ref

      @withActivation
      class Test extends Component {
        componentDidMount() {
          console.log(this.outside) // will log <div /> instance
          console.log(this.inside) // will log <div /> instance
        }
       
        render() {
          return (
            <div>
              <div ref={ref => {
                this.outside = ref
              }}>
                Outside KeepAlive
              </div>
              <KeepAlive>
                <div ref={ref => {
                  this.inside = ref
                }}>
                  Inside KeepAlive
                </div>
              </KeepAlive>
            </div>
          )
        }
      }
    2. 对 Context 的破坏性影响,需手动修复

      问题情景参考:https://github.com/StructureBuilder/react-keep-alive/issues/36

      (
        <Provider value={1}>
          {show && (
            <KeepAlive>
              <Consumer>
                {context => ( // 由于渲染层级被破坏,此处无法正常获取 context
                  <Test contextValue={context} />
                )}
              </Consumer>
            </KeepAlive>
          )}
          <button onClick={toggle}>toggle</button>
        </Provider>
      )

      修复方式任选一种

      • 使用从 react-activation 导出的 createContext 创建上下文

      • 使用从 react-activation 导出的 fixContext 修复受影响的上下文

      ...
      import { createContext } from 'react-activation'
       
      const { Provider, Consumer } = createContext()
      ...
      // or
      ...
      import { createContext } from 'react'
      import { fixContext } from 'react-activation'
       
      const Context = createContext()
      const { Provider, Consumer } = Context
       
      fixContext(Context)
      ...
    3. 对依赖于 React 层级的功能造成影响,如下

      • Error Boundaries(已修复)
      • React.Suspense & React.lazy(已修复)
      • React 合成事件冒泡失效
      • 其他未发现的功能

    Install

    npm i react-keep-active

    DownloadsWeekly Downloads

    1

    Version

    1.0.0

    License

    ISC

    Unpacked Size

    1.17 MB

    Total Files

    10

    Last publish

    Collaborators

    • avatar