类 Vue
<keep-alive>的 React 18/19 组件缓存插件 — 缓存组件状态,避免切换时重新渲染销毁,提升性能。
- ✅ 状态缓存 — 切换组件时保留 state、DOM、滚动位置
- ✅ 生命周期钩子 —
useActivated/useDeactivated,对标 VueonActivated/onDeactivated - ✅ LRU / FIFO 淘汰策略 — 自动管理缓存内存,防止无限增长
- ✅ include / exclude 过滤 — 精确控制哪些组件需要缓存
- ✅ React Router 集成 —
<KeepAliveRouteOutlet>替代<Outlet>,支持 v6 / v7 - ✅ 手动控制 —
drop()/refresh()/getCacheKeys() - ✅ TypeScript — 完整类型声明
- ✅ 零依赖 — 只依赖 React 本身
npm install react-keep-alive-max
# 或
pnpm add react-keep-alive-max
# 或
yarn add react-keep-alive-maximport { KeepAliveScope, KeepAlive } from 'react-keep-alive-max';
function App() {
const [tab, setTab] = useState('home');
return (
// 1. 在顶层包裹 KeepAliveScope
<KeepAliveScope max={10} strategy="LRU">
<nav>
<button onClick={() => setTab('home')}>首页</button>
<button onClick={() => setTab('list')}>列表</button>
</nav>
{/* 2. 用 KeepAlive 包裹需要缓存的组件 */}
{tab === 'home' && (
<KeepAlive cacheKey="home">
<HomePage />
</KeepAlive>
)}
{tab === 'list' && (
<KeepAlive cacheKey="list">
<ListPage />
</KeepAlive>
)}
</KeepAliveScope>
);
}import { useActivated, useDeactivated } from 'react-keep-alive-max';
function ListPage() {
const [data, setData] = useState([]);
// 从缓存恢复时触发(类似 Vue 的 onActivated)
useActivated(() => {
console.log('页面被激活,刷新数据...');
fetchLatestData().then(setData);
});
// 被推入缓存时触发(类似 Vue 的 onDeactivated)
useDeactivated(() => {
console.log('页面被缓存,暂停轮询...');
stopPolling();
});
return <List data={data} />;
}import { useKeepAliveContext } from 'react-keep-alive-max';
function AdminPanel() {
const { drop, refresh, getCacheKeys, activeKey } = useKeepAliveContext();
return (
<div>
<p>当前激活:{activeKey}</p>
<p>已缓存页面:{getCacheKeys().join(', ')}</p>
{/* 刷新指定页面(下次访问时重新挂载) */}
<button onClick={() => refresh('list')}>刷新列表页</button>
{/* 清空所有缓存 */}
<button onClick={() => drop()}>清空缓存</button>
</div>
);
}import { KeepAliveScope } from 'react-keep-alive-max';
import { KeepAliveRouteOutlet } from 'react-keep-alive-max/router';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function Layout() {
return (
<div>
<nav>
<Link to="/">首页</Link>
<Link to="/list">列表</Link>
<Link to="/form">表单</Link>
</nav>
{/* 替换 <Outlet /> */}
<KeepAliveRouteOutlet
exclude={['/login', '/register']} // 这些路由不缓存
/>
</div>
);
}
function App() {
return (
<KeepAliveScope max={8}>
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<HomePage />} />
<Route path="list" element={<ListPage />} />
<Route path="form" element={<FormPage />} />
<Route path="login" element={<LoginPage />} />
</Route>
</Routes>
</BrowserRouter>
</KeepAliveScope>
);
}顶层 Provider,必须包裹在所有 <KeepAlive> 外层。
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
max |
number |
10 |
最大缓存组件数量 |
strategy |
`'LRU' | 'FIFO'` | 'LRU' |
核心缓存组件。
| Prop | 类型 | 必填 | 说明 |
|---|---|---|---|
cacheKey |
string |
✅ | 缓存唯一标识 |
include |
`string[] | RegExp` | — |
exclude |
`string[] | RegExp` | — |
onActivated |
() => void |
— | 激活时回调 |
onDeactivated |
() => void |
— | 停用时回调 |
组件从缓存恢复(激活)时调用 callback。
组件被推入缓存(停用)时调用 callback。
返回缓存控制方法:
interface KeepAliveControls {
drop: (key?: string) => void; // 销毁缓存(不传 = 清空全部)
refresh: (key: string) => void; // 刷新缓存(销毁后重建)
getCacheKeys: () => string[]; // 获取所有缓存 key
activeKey: string | null; // 当前激活的 cacheKey
}替代 <Outlet> 的路由缓存组件,兼容 react-router-dom v6 / v7。
| Prop | 类型 | 说明 |
|---|---|---|
cacheKey |
(pathname, search) => string |
自定义 key 生成函数,默认使用pathname |
include |
`string[] | RegExp` |
exclude |
`string[] | RegExp` |
onActivated |
(key: string) => void |
路由激活回调 |
onDeactivated |
(key: string) => void |
路由停用回调 |
CacheManager 在 caches.size > max 时自动淘汰,只会淘汰**非激活(inactive)**的缓存项,不会删除正在显示的组件:
LRU(最近最少使用):
inactive 项按 lastActiveTime 升序排列,最久未访问的先被淘汰
FIFO(先进先出):
inactive 项按 createdTime 升序排列,最早创建的先被淘汰
市面上主流的 React 缓存方案大致分为两类,各有不同的实现路线和权衡:
| 维度 | 本库 | react-activation | keepalive-for-react |
|---|---|---|---|
| React 版本兼容 | ✅ 18 / 19 原生 | ✅ 18 / 19 | |
| 实现原理 | Portal + DOM 搬运 | Fiber 内部 hack | Portal + CSS 隐藏 |
| React 内部 API | ✅ 全公开 API | ❌ 使用私有 Fiber 字段 | ✅ 公开 API |
| Concurrent Mode | ✅ 完全兼容 | ✅ 兼容 | |
| 布局影响 | ✅ 无(物理移出) | ✅ 无 | ❌ 占位 |
| 生命周期钩子 | ✅ 响应式传播 | ✅ 支持 | |
| React Router v6 | ✅ 原生集成 | ✅ 支持 | |
| TypeScript | ✅ 完整 | ✅ 完整 | |
| 零依赖 | ✅ | ❌ | ✅ |
| 包大小 | ~5KB | ~15KB | ~8KB |
react-activation 是目前 Star 最多的同类方案。它通过直接操作 React Fiber 内部结构(__reactFiber、_reactInternals)来"接管"子树的渲染:
// react-activation 内部实现(简化示意)
const fiber = instance.__reactFiber // ❌ 访问私有字段
fiber.child.stateNode.forceUpdate() // ❌ 强制更新 Fiber 节点风险:React 的私有 Fiber 字段在任何版本都可能改名/移除。React 18 的 Concurrent Mode 引入了调度优先级,直接操作 Fiber 可能导致渲染状态不一致,在 React 19 中已出现多个已知 Bug。
本库的做法:零 Fiber 操作,完全使用公开 API(createPortal、useLayoutEffect、useState),不受 React 版本迭代影响。
keepalive-for-react 与本库思路相近,同样使用 Portal,但选择用 CSS display: none 控制显隐:
keepalive-for-react 的隐藏方式:
<div style="display: none">
<Portal → 缓存的组件 /> ← 组件仍在 DOM 中,只是不可见
</div>
本库的隐藏方式:
<div style="display: none"> ← cacheRoot,始终隐藏
← container 物理移入(appendChild)
</div>
<placeholder /> ← container 物理移出(appendChild 回来)
display: none 的方案存在两个问题:
- 布局污染:隐藏容器仍然参与 CSS Stacking Context 计算,影响
z-index、overflow等属性 - 滚动位置:部分浏览器在
display: none时会重置scrollTop
本库的 DOM 物理搬运彻底规避了这两个问题。
⚠️ 不要与React.StrictMode同时使用(StrictMode 双重渲染会干扰缓存逻辑)⚠️ cacheKey必须唯一且稳定,避免使用随机值⚠️ useActivated/useDeactivated必须在<KeepAliveScope>内部使用
# 安装依赖
npm install
# 启动 Demo
npm run demo:install
npm run demo
# 运行测试
npm test
# 构建
npm run buildMIT © 2026