react-keep-active
v1.0.0
Published
<KeepAlive /> for React like <keep-alive /> in vue
Downloads
2
Maintainers
Readme
React Activation
Vue 中 <keep-alive />
功能在 React 中的实现
定位与 react-keep-alive 相同
但修复了 https://github.com/StructureBuilder/react-keep-alive/issues/36 中的部分问题
配合 babel 预编译实现更稳定的 KeepAlive 功能
兼容性
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
装饰器
使用 componentDidActivate
与 componentWillUnactivate
对应激活与缓存两种状态
FunctionComponent
则分别使用 useActivate
与 useUnactivate
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
时
返回值为上述 Boolean
或 Array
,依照上述说明生效
手动控制缓存
给需要控制缓存的
<KeepAlive />
标签增加name
属性使用
withAliveScope
或useAliveController
获取控制函数drop(name):
按 name 卸载缓存状态下的
<KeepAlive>
节点,name 可选类型为String
或RegExp
,注意,仅卸载命中<KeepAlive>
的第一层内容,不会卸载<KeepAlive>
中嵌套的、未命中的<KeepAlive>
dropScope(name)
按 name 卸载缓存状态下的
<KeepAlive>
节点,name 可选类型为String
或RegExp
,将卸载命中<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 由实现原理引发的额外问题
<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
目前暂无处理方式,可使用setTimeout
或nextTick
延时获取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> ) } }
对 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) ...
对依赖于 React 层级的功能造成影响,如下
- [x] ~~Error Boundaries~~(已修复)
- [x] ~~React.Suspense & React.lazy~~(已修复)
- [ ] React 合成事件冒泡失效
- [ ] 其他未发现的功能