React实现keepAlive
keepAlive是什么
keep-alive是vue中的内置组件,使用keep-alive包裹组件可以在该组件的切换过程中将页面的状态缓存在内存中,界面切换后被包裹的组件实例不会被销毁,防止界面重复渲染DOM,并且可以防止在页面切换过程中组件的数据丢失
使用场景
如下图所示,想要实现以下效果:
1.第一步: 在user路由下的输入框中输入内容
2.第二步: 切换至home路由
3.第三步:再切换回来,保留上次输入的值
即:切换路由时缓存之前的组件,保留之前输入的值不被清空
vue项目可以使用内置的keep-alive组件包裹实现。react可以通过目前市面上的其他库来实现,例如React Activation和react-keep-alive等等,我们也可以手动实现。
代码实现
核心功能实现在keepAlive目录中
/KeepAlive.js
包裹在需要缓存的组件外侧,通过Provider把状态和方法传递给子组件,如果判断出reactElement已经传过来了,但是对应的nodes数组还是为null的话就触发相应的reducer把该nodes重新赋值
import { useCallback, useReducer } from "react"
import {KeepAliveReducer} from './KeepAliveReducer'
import * as actionTypes from './types'
import { KeepAliveContext } from "./KeepAliveContext"
function KeepAlive(props) {
console.log('KeepAlive props=>', props);
/**
* {
* home: {
* keepAliveId: 'home',
* reactElement: reactElement,
* nodes: nodes,
* status: create | created
* },
* user: {
* keepAliveId: 'user',
* reactElement: reactElement,
* nodes: nodes,
* status: create | created
* }
* }
*/
const [keepAliveStates, dispatch] = useReducer(KeepAliveReducer, {})
const setKeepAliveState = useCallback(({ reactElement, keepAliveId }) => {
if(!keepAliveStates[keepAliveId]) {
dispatch({
type: actionTypes.CREATING,
payload: {
keepAliveId,
reactElement
}
})
}
}, [keepAliveStates])
return (
<KeepAliveContext.Provider value={{
keepAliveStates,
setKeepAliveState,
dispatch
}}>
{ props.children }
{
Object.values(keepAliveStates).map(({ keepAliveId, reactElement }) => (
<div key={ keepAliveId } ref={(node) => {
console.log(keepAliveStates);
if(node && !keepAliveStates[keepAliveId].nodes) {
dispatch({
type: actionTypes.CREATED,
payload: {
keepAliveId,
nodes: [...node.childNodes]
}
})
}
}}>{ reactElement }</div>
))
}
</KeepAliveContext.Provider>
)
}
export default KeepAlive
/keepAliveReducer.js
不同时机更新state的reducer
import * as actionTypes from './types'
/**
*
* @param {*} state keepAliveStates
* @param {*} action { type, payload }
*/
export function KeepAliveReducer(state, action) {
const { type, payload } = action
const { keepAliveId, reactElement, nodes } = payload
switch(type) {
case actionTypes.CREATING:
console.log(state);
return {
...state,
[keepAliveId]: {
keepAliveId,
reactElement,
status: type,
nodes: null
}
}
case actionTypes.CREATED:
console.log(state);
return {
...state,
[keepAliveId]: {
...state[keepAliveId],
status: type,
nodes
}
}
default: return state
}
}
/keepAliveContext.js
自定义context
import { createContext } from 'react'
export const KeepAliveContext = createContext()
/transfer.js
每次进入判断有无节点数组,没有则触发reducer把当前的组件赋值给state的
reactElement属性,如果有则通过appendChild方法把节点添加至当前组件下
/* eslint-disable react-hooks/rules-of-hooks */
import { useContext, useEffect, useRef } from "react"
import { KeepAliveContext } from "./keepAliveContext"
export function KeepAliveTransfer(KeepAliveComponent, keepAliveId) {
return function(props) {
const _ref = useRef(null)
const { keepAliveStates, setKeepAliveState } = useContext(KeepAliveContext)
// console.log('keepAliveStates', keepAliveStates);
// console.log('setKeepAliveState', setKeepAliveState);
useEffect(() => {
const state = keepAliveStates[keepAliveId]
if(state && state.nodes) {
console.log('有节点', state);
state.nodes.forEach(node => _ref.current.appendChild(node))
} else {
console.log('无节点', state);
setKeepAliveState({
reactElement: <KeepAliveComponent { ...props }/>,
keepAliveId
})
}
}, [keepAliveStates, setKeepAliveState, props])
return (
<div ref={ _ref }></div>
)
}
}
/types.js
export const CREATING = 'CREATING'
export const CREATED = 'CREATED'