【React】实现类似vue的keepAlive组件的持久化
1. 创建 KeepAliveContext.js
首先,我们需要创建一个 KeepAliveContext
来存储需要持久化的组件实例。
// src/KeepAliveContext.js
import { createContext, useState } from "react"
export const KeepAliveContext = createContext()
function KeepAliveProvider(props) {
/**
* {
* home: {
* id: 'home',
* element: element,
* nodes: nodes,
* },
* user: {
* id: 'user',
* element: element,
* nodes: nodes,
* }
* }
*/
const [cached, setCached] = useState({})
return (
<KeepAliveContext.Provider value={{ cached, setCached }}>
{props.children}
{Object.values(cached).map(({ id, element, nodes }) => (
<div
key={id}
ref={(node) => {
if (node && !nodes) {
// 缓存实例的 childNodes
setCached({
...cached,
[id]: { id, element, nodes: [...node.childNodes] }
})
}
}}
>
{element}
</div>
))}
</KeepAliveContext.Provider>
)
}
export default KeepAliveProvider
2. 创建 keepAlive.js
接下来,我们创建 keepAlive
函数,该函数负责缓存组件的实例。
// src/KeepAlive.js
/* eslint-disable react-hooks/rules-of-hooks */
import { useContext, useEffect, useRef } from "react"
import { KeepAliveContext } from "./KeepAliveContext"
export default function KeepAlive(KeepAliveComponent, id) {
return function (props) {
const _ref = useRef(null)
const { cached, setCached } = useContext(KeepAliveContext)
useEffect(() => {
if (!cached[id]) {
// 首次初始化时缓存组件实例
setCached({
...cached,
[id]: { id, element: <KeepAliveComponent {...props} />, nodes: null }
})
} else {
const { nodes } = cached[id]
if (nodes && Array.isArray(nodes)) {
nodes.forEach((node) => _ref.current.appendChild(node))
}
}
}, [cached, setCached, props])
return <div ref={_ref}></div>
}
}
3. 创建示例组件 Counter.js
这是一个简单的计数器组件,用于测试 KeepAlive
功能。
// src/components/Counter.js
import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default Counter;
4. 创建应用主组件 App.js
在这个主组件中,我们使用 KeepAliveProvider
和 keepAlive
函数来管理组件的持久化。
// src/App.js
import React, { useState } from 'react';
import { KeepAliveProvider } from './KeepAliveContext';
import keepAlive from './keepAlive';
import Counter from './components/Counter';
const AliveCounter = keepAlive('counter', Counter);
const App = () => {
const [showCounter, setShowCounter] = useState(true);
return (
<KeepAliveProvider>
<div>
<button onClick={() => setShowCounter(!showCounter)}>
{showCounter ? 'Hide' : 'Show'} Counter
</button>
{showCounter && <AliveCounter />}
</div>
</KeepAliveProvider>
);
};
export default App;
5. 入口文件 index.js
确保在你的入口文件中正确引入 App
组件并渲染它。
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
总结
通过这个完整的案例,我们实现了一个类似于 Vue 的 KeepAlive
功能的组件,通过缓存组件的实例和其 childNodes,在重新渲染时通过读取childNodes恢复组件。通过这种方式,我们可以确保组件的实际 DOM 节点在卸载和重新挂载之间保持不变,从而实现了组件的持久化。