目 录CONTENT

文章目录

React-Activation使用

Administrator
2020-07-24 / 0 评论 / 3 点赞 / 11755 阅读 / 13305 字 / 正在检测是否收录...

React-Activation使用

该份笔记大部转自React-Activation的github网站,如有详细了解的同学可以自行上github去查阅资料

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

该组件是react开发过程当中使用得比较频繁的一个组件,它与vue开发过程当中的<keep-alive>功能相似,都可以缓存一整 个页面。与该组件相类似的不家另一个组件react-keep-alive


兼容性

  • React v16+
  • Preact v10+
  • 兼容 SSR

安装

$ yarn add react-activation
# 或者
$ npm install react-activation

使用方式

  1. babel 配置文件 .babelrc 中增加 react-activation/babel 插件

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

    {
      "plugins": [
        "react-activation/babel"
      ]
    }
    
  2. 业务代码中,在不会被销毁的位置放置 <AliveScope> 外层,一般为应用入口处

    注意:与 react-routerreact-redux 配合使用时,需要将 <AliveScope> 放置在 <Router><Provider> 内部

    // entry.js
    
    import React from 'react'
    import ReactDOM from 'react-dom'
    import { AliveScope } from 'react-activation'
    
    import Test from './Test'
    
    ReactDOM.render(
        <AliveScope>
            <Test />
        </AliveScope>,
        document.getElementById('root')
    )
    
  3. <KeepAlive> 包裹需要保持状态的组件

    如例子中的 <Counter> 组件

    // Test.js
    
    import React, { useState } from 'react'
    import KeepAlive from 'react-activation'
    
    function Counter() {
      const [count, setCount] = useState(0)
    
      return (
        <div>
          <p>count: {count}</p>
          <button onClick={() => setCount(count => count + 1)}>Add</button>
        </div>
      )
    }
    
    function Test() {
      const [show, setShow] = useState(true)
    
      return (
        <div>
          <button onClick={() => setShow(show => !show)}>Toggle</button>
          {show && (
            <KeepAlive>
              <Counter />
            </KeepAlive>
          )}
        </div>
      )
    }
    
    export default Test
    

生命周期

ClassComponent 可配合 withActivation 装饰器

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

FunctionComponent 则分别使用 useActivateuseUnactivate hooks 钩子

...
import KeepAlive, { useActivate, useUnactivate, withActivation } from 'react-activation'

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

保存滚动位置

默认为 true

<KeepAlive /> 会检测它的 children 属性中是否存在可滚动的元素,然后在 componentWillUnactivate 之前自动保存滚动位置,在 componentDidActivate 之后恢复保存的滚动位置

如果你不需要 <KeepAlive /> 做这件事,可以将 saveScrollPosition 属性设置为 false

<KeepAlive saveScrollPosition={false} />

如果你的组件共享了屏幕滚动容器如 document.bodydocument.documentElement, 将 saveScrollPosition 属性设置为 "screen" 可以在 componentWillUnactivate 之前自动保存共享屏幕容器的滚动位置

<KeepAlive saveScrollPosition="screen" />

多份缓存

同一个父节点下,相同位置的 <KeepAlive> 默认会使用同一份缓存

例如下述的带参数路由场景,/item 路由会按 id 来做不同呈现,但只能保留同一份缓存

<Route
  path="/item/:id"
  render={props => (
    <KeepAlive>
      <Item {...props} />
    </KeepAlive>
  )}
/>

类似场景,可以使用 <KeepAlive>id 属性,来实现按特定条件分成多份缓存

<Route
  path="/item/:id"
  render={props => (
    <KeepAlive id={props.match.params.id}>
      <Item {...props} />
    </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 (
      ...
    )
  }
}
...

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 合成事件冒泡失效
    • 其他未发现的功能

与路由转场动画结合

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom'
import KeepAlive, { AliveScope } from 'react-activation'
import { CSSTransition, TransitionGroup } from 'react-transition-group'

import 'animate.css/animate.min.css'

class Test extends React.Component {
  state = {
    number: 0
  }

  handleClick = () => {
    this.setState(({ number }) => ({
      number: number + 1
    }))
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick}>
          Click the button to increase the number
        </button>
        <div>Number: {this.state.number}</div>
      </div>
    )
  }
}

function App() {
  return (
    <div>
      <ul>
        <li>
          <Link to="/one">one</Link>
        </li>
        <li>
          <Link to="/two">two</Link>
        </li>
      </ul>

      <Route
        render={({ location }) => {
          return (
            <TransitionGroup>
              <CSSTransition
                key={location.key}
                classNames={{
                  enter: 'animated',
                  enterActive: 'fadeInDown',
                  exit: 'animated',
                  exitActive: 'fadeOutDown'
                }}
                timeout={1000}
                mountOnEnter
                unmountOnExit
              >
                <div>
                  <Switch location={location}>
                    <Route
                      path="/one"
                      render={props => (
                        <KeepAlive>
                          <Test {...props} />
                        </KeepAlive>
                      )}
                    />
                    <Route path="/two" render={() => 'This is two'} />
                  </Switch>
                </div>
              </CSSTransition>
            </TransitionGroup>
          )
        }}
      />
    </div>
  )
}

ReactDOM.render(
  <Router>
    <AliveScope>
      <App />
    </AliveScope>
  </Router>,
  document.getElementById('root')
)
3

评论区