Keepalive 伪代码
# KeepaliveScope
- HOC经典应用,包裹一层 Context
const KeepaliveContext = React.createContext({})
function Scope({ children }) {
/* 自定义 Hooks 分离出所需的 state */
const keeper = useKeep()
/* 很像 redux,使用 dispatch 去单向修改 state */
const { state, dispatch } = keeper
/* 重点:HOC children 的写法*/
const renderChildren = children
/* 包裹一层 Context,注意一定要使用 useMemo */
const contextValue = useMemo(() => {
return {
dispatch:dispatch
yyy:yyyy
}
}, [keeper])
return <KeepaliveContext.Provider value={contextValue}>
{/* Scope 是 Function Component 的写法*/}
{renderChildren}
{ /* 用一个列表渲染 */ }
{cacheList.map(item => <ScopeItem {...item} dispatch={dispatch.bind(keeper)} key={item.cacheId} />)}
</KeepaliveContext.Provider>
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
注意:当使用的是实例方法的,那在使用时需要 bind
下指针。
# 如何渲染 children
?
直接渲染
直接渲染
children
渲染时需要带上 props
通过构造一个
renderWithChildren
# ScopeItem
通过 cacheList
做了一层渲染拦截的效果,渲染逻辑是:
原本由:KeepaliveItem
渲染的真实的 DOM
,缓存在 cacheList
的 children
上,交给 ScopeItem
去代替 KeepaliveItem
控制渲染。
步骤如下:
将原本的
children
通过createPortal
渲染到document.body
节点上,伪代码如下:注:此时应设置
display
为none
const children = cacheList.children const element = ReactDOM.createPortal(<div style={{display:"none"}}>{children}</div>,document.body)
1
2为了后续能控制这部分代码的渲染,所以还需要加上
ref
const currentDOM = useRef(); const element = ReactDOM.createPortal(<div ref={currentDOM} style={{display:"none"}}>{children}</div>,document.body)
1
2
3cacheList
中还存放着当前组件的生命周期当处于
active
的状态时,则需要将display
为block
状态的jsx
对象由parenNode
去渲染。if(status === "active"){ cacheList.parentNode.appendChild(currentDOM.current) }
1
2
3当组件从
active
状态变为unactive
的状态时,则重新挂载到body
上。if(status==="unactive"){ document.body.appendChild(currentDOM.current) }
1
2
3如果是封装组件库的话,其实还可以添加生命周期,如下:
if(status==="unactive"){ document.body.appendChild(currentDOM.current); fake_LifeCircle(); /* 实际代码中是使用 Keepalive 去(事件发布订阅机制)去缓存状态的,如下:*/ dispatch({ type: "unactived", payload: cacheId }) }
1
2
3
4
5
6
7
8
9
这里需要注意一个细节,将
DOM
结构完全摘出来以后,就无法做到与父元素渲染子元素也随着更新的这样一个状态(ps:感觉又有些像 Vue,响应式编程)解决方案:在
cacheList
存的时候多存一个标志位,用于模拟父组件的更新效果。即,让
created
以及update
生命周期时,多保存一个updater:{}
的状态,用于forceUpdate
组件。改写,第一个步骤的代码:
const element = ReactDOM.createPortal( <div style={{display:"none"}}> - {children} + {useMemo(()=> renderChildren(),[updater])} </div>,document.body)
1
2
3
4
5# KeepaliveItem
使用方式:
<KeepaliveItem cacheId="demo-xxx"> <Child /> </KeepaliveItem>
1
2
3主要目的就是构造对象存入
cacheList
中,此时cacheList
是保存在keepaliveScope
中的,因此,只能使用useContext
的方式去取修改方法,如:const {cacheDispatch} = useContext(keepaliveContext)
1将
KeepaliveItem
中的生命周期,缓存到cacheList
中