1, 简介
react 实现keep-alive功能。对比了几个,最终选择 react-activation
简单使用:
import {BrowserRouter ,Switch,Route} from "react-router-dom";
import { AliveScope,KeepAlive } from 'react-activation';
<BrowserRouter>
<AliveScope>
<Switch>
<Route path="/item/:id" render={props => (
<KeepAlive id={props.match.params.id}>
<Item {...props} />
</KeepAlive> )}/>
<Route .../>
</Switch>
</AliveScope>
</BrowserRouter>
生命周期函数:在激活和离开的时候触发
import { useActivate, useUnactivate, withActivation } from 'react-activation'
缓存控制函数:获取或删除缓存
import { withAliveScope, useAliveController } from 'react-activation'
2,实现原理
核心思想:将渲染过的组件(div.ka-content)存起来,等到再次用,从缓存拿到,使用js的 appendchild 到 KeepAlive中的 (div.ka-wrapper)
你可以观察你页面上的元素结构
<div id="root">
<div class="ka-wrapper">
<div class="ka-content">
<div class="xxx">你的组件xxx</div>
</div>
</div>
<div _ka="/3YV" style="display:none;">
<div _ka="/Mfull"></div>
<div _ka="/Mfull"></div>
</div>
</div>
KeepAlive组件仅仅是一个placeholder(div.ka-wrapper),并不渲染children,children在Keeper 组件(div _ka="/Mfull")中渲染,存储在storeMap中,再次渲染时去缓存storeMap中根据id拿到children插入到placeholder
react-node-key NodeKey组件:添加key属性,babel转换(版本:0.3.5符号_ka;0.10.2符号_nk),帮助 react-activation 在运行时按渲染位置生成唯一的缓存 id 标识
可参考 一些关于react的keep-alive功能 这篇自己diy的。
3,源码核心结构
3.1,AliveScope组件 -- core/AliveScope.js
三个主要存储结构:
store = new Map() //[cache],内置Keeper组件中控制添加
nodes = new Map() // 主要用于render里面遍历,KeepAlive组件中控制添加
keepers = new Map() //[KeeperRef] 用于操作Keeper
handleNodes 方法:执行Keeper中方法
dom结构:这里children就是对应的KeepAlive组件,nodes对应缓存的组件
<AliveScopeProvider value={this.helpers}>
{children}
<div style={{ display: 'none' }}>
{[...this.nodes.values()].map(({ children, ...props }) => (
<Keeper
key={props.id}
{...props}
scope={this}
store={this.store}
keepers={this.keepers}
ref={(keeper) => {
this.keepers.set(props.id, keeper)
}}
>
{children}
</Keeper>
))}
</div>
</AliveScopeProvider>
3.2,全局状态 -- core/context/index.js
如上 AliveScopeProvider 中,主要结构如下:
// 静态化节点上下文内容,防止重复渲染
helpers = {
keep: this.keep,
update: this.update,
drop: this.drop,
refresh: this.refresh,
getCache: this.getCache,
getNode: this.getNode,
...
}
3.3,KeepAlive组件 -- core/KeepAlive.js
- render div className=“ka-wrapper”
组件生命周期函数:
constructor:执行init()->
添加nodes Map、
inject()挂载激活的node到 ka-wrapper、
触发Keeper的[LIFECYCLE_ACTIVATE, LIFECYCLE_UNACTIVATE]方法
shouldComponentUpdate:提前触发组件更新
componentWillUnmount:执行eject()移除缓存
主要方法:
init:this.nodes.set(id, {...}]
inject: cache.nodes.forEach((node) => {
this.placeholder.appendChild(node)
})
eject: cache.nodes.forEach((node) => {
this.placeholder.removeChild(node)
})
3.4,Keeper -- core/Keeper.js
- render div className=“ka-content”
- Fragment组件包裹,不渲染到页面
- nodes缓存的node,在keepAlive组件init的时候插入到KeepAlive组件对应placeholder (className=“ka-wrapper”)
每个激活的KeepAlive组件对应一个Keeper,主要用于观测组件状态,触发响应生命周期函数
组件生命周期函数:
componentDidMount:store.set(id, this.cache)
cache结构:this.cache = {
listeners, //使用useActivate, useUnactivate, withActivation的组件
aliveNodesId: [],
inited: false,
cached: false,
wrapper: node,
nodes,
[LIFECYCLE_ACTIVATE]: () => this[LIFECYCLE_ACTIVATE](),
[LIFECYCLE_UNACTIVATE]: () => this[LIFECYCLE_UNACTIVATE](),
}
componentWillUnmount: store.delete(id);keepers.delete(id);
主要方法:
attach:listeners订阅中心
listeners.set(ref, {
[LIFECYCLE_ACTIVATE]: () => run(ref, LIFECYCLE_ACTIVATE),
[LIFECYCLE_UNACTIVATE]: () => run(ref, LIFECYCLE_UNACTIVATE),
})
refresh:按name刷新缓存状态下的KeepAlive组件
drop:nodes.delete(id)
;[LIFECYCLE_ACTIVATE]() {}、 ;[LIFECYCLE_UNACTIVATE]() {}:KeepAlive组件执行construct的时候触发,发布listeners事件
3.5,生命周期相关 -- core/lifecycles.js
在激活或者失活执行相应钩子函数
- withActivation(componentDidActivate、componentWillUnactivate)
- useActivate、useUnactivate
加载的时候 执行 Keeper的 attach 方法,订阅生命周期事件listeners.set()
3.6,缓存控制 -- core/withAliveScope.js
- withAliveScope
- useAliveController
借用 aliveScopeContext 里面 来控制
欢迎关注我的前端自检清单,我和你一起成长