8.7 KiB
路由页面缓存使用说明
本项目使用 react-activation 实现 Layout 内页面的 KeepAlive 缓存。切走再回来时,页面组件不会卸载,滚动位置和本地 state 会保留。
1. 整体结构
main.tsx
└─ AliveScope ← 全局必须,否则 KeepAlive 无效
└─ RouterProvider
└─ LayoutRoot ← 带侧栏的业务页统一走这里
├─ RouteCacheController ← LRU 淘汰(在 KeepAlive 树外)
└─ Layout
└─ CachedLayoutOutlet ← 按规则决定是否包 KeepAlive
└─ 各页面组件
| 文件 | 职责 |
|---|---|
src/main.tsx |
挂载 AliveScope |
src/router/reactActivationSetup.ts |
React 18 兼容(关 autoFreeze、修 Context) |
src/router/LayoutRoot.tsx |
Layout 只挂一次 + 组装缓存控制器 |
src/router/CachedLayoutOutlet.tsx |
判断是否缓存、包裹 KeepAlive |
src/router/RouteCacheController.tsx |
超出上限时 LRU 淘汰 |
src/router/routeCache.ts |
缓存规则配置中心(改这里最多) |
src/router/index.tsx |
路由表:哪些页在 LayoutRoot 下 |
2. 默认哪些路由会缓存
会缓存
挂在 LayoutRoot 的 children 里、且 isLayoutRouteCacheable(pathname) 返回 true 的路由。
当前默认缓存:
| 路径 | 页面 |
|---|---|
/explore |
探索 |
/characters |
角色 |
/characters-create |
角色创建 |
/subscribe |
订阅 |
/profile |
个人中心 |
/legal |
法律条款 |
/content/page |
内容页 |
不会缓存
-
不在
LayoutRoot下的路由(根本不会经过CachedLayoutOutlet)
例如:/index、/404 -
在
isLayoutRouteCacheable里被排除的路由
当前排除:/create及所有/create/*子路由
3. 如何让新页面加入缓存
需要同时满足两点。
步骤 A:路由挂到 LayoutRoot 下
在 src/router/index.tsx 的 LayoutRoot → children 中增加路由:
{
element: <LayoutRoot />,
children: [
// ...
{
path: "my-new-page", // 对应 /my-new-page
element: <MyNewPage />,
},
],
},
只要页面走 LayoutRoot,且未被 isLayoutRouteCacheable 排除,就会自动进入 KeepAlive,无需再改 CachedLayoutOutlet。
步骤 B:确认未被排除规则拦截
打开 src/router/routeCache.ts,检查 isLayoutRouteCacheable 是否对该 pathname 返回 true。
4. 如何让页面不加入缓存
有三种方式,按场景选择。
方式一:不挂在 LayoutRoot 下(整页无侧栏)
适合落地页、404 等本来就不要 Layout 的页面。
// 与 LayoutRoot 平级,直接渲染
{
path: ROUTES.INDEX,
element: (
<Suspense fallback={<RouteLoading />}>
<Index />
</Suspense>
),
},
方式二:在 isLayoutRouteCacheable 中排除(推荐)
适合:有 Layout 侧栏,但希望每次进入都重新加载(如 /create)。
编辑 src/router/routeCache.ts:
/** 不参与 KeepAlive 的路由前缀 / 精确路径 */
const LAYOUT_ROUTE_CACHE_EXCLUDED_EXACT = new Set<string>([
ROUTES.CREATE,
ROUTES.CHARACTERS_CREATE, // 示例:角色创建也不缓存
]);
const LAYOUT_ROUTE_CACHE_EXCLUDED_PREFIXES = [
`${ROUTES.CREATE}/`, // /create/* 全部不缓存
] as const;
export function isLayoutRouteCacheable(pathname: string): boolean {
if (LAYOUT_ROUTE_CACHE_EXCLUDED_EXACT.has(pathname)) {
return false;
}
if (LAYOUT_ROUTE_CACHE_EXCLUDED_PREFIXES.some((prefix) => pathname.startsWith(prefix))) {
return false;
}
return true;
}
当前项目用的是内联判断,逻辑等价于:
export function isLayoutRouteCacheable(pathname: string): boolean {
return pathname !== ROUTES.CREATE && !pathname.startsWith(`${ROUTES.CREATE}/`);
}
方式三:把路由移出 LayoutRoot
有侧栏需求但不想缓存时,更推荐方式二;移出 LayoutRoot 会导致侧栏也消失,一般不要用。
5. 缓存 key 与「同一页多份缓存」
默认 getLayoutRouteCacheKey 使用完整 pathname:
export function getLayoutRouteCacheKey(pathname: string): string {
return pathname;
}
含义:
/explore与/characters各一份缓存- 带 query 的路径(如
/profile?tab=1)pathname 不变时,仍共用同一份缓存(search 不参与 key)
若多个 pathname 要共用一份缓存(例如某模块下多个 Tab 共用一个壳),可在 getLayoutRouteCacheKey 里做归一化:
export function getLayoutRouteCacheKey(pathname: string): boolean {
if (pathname.startsWith("/some-module/")) {
return "/some-module";
}
return pathname;
}
注意:共用 key 时,子路由切换由 Router Outlet 驱动,子页面仍可能随 Outlet 卸载。仅在你明确需要「整壳保留、内部自行切换」时使用。
6. 缓存数量上限(LRU)
在 src/router/routeCache.ts:
export const LAYOUT_ROUTE_CACHE_MAX = 8;
超出后,RouteCacheController 会按最近访问顺序淘汰最久未用的可缓存页面。当前页不会被淘汰。
只统计 isLayoutRouteCacheable(key) === true 的节点,被排除的路由(如 /create)不会占名额。
7. 页面内:再次显示时刷新数据
缓存只保留组件实例,不会自动重拉接口。若「切回来要更新数据」,在页面里用:
import { useActivate } from "react-activation";
function ExplorePage() {
useActivate(() => {
// 从缓存恢复、重新可见时执行
refetchList();
});
// ...
}
隐藏/切走时可配合:
import { useUnactivate } from "react-activation";
useUnactivate(() => {
// 页面被缓存隐藏时(可选)
});
8. 手动清除缓存
在任意 AliveScope 内的组件中(建议 Layout 或全局逻辑,不要放在 KeepAlive 子树深处):
import { useAliveController } from "react-activation";
function SomeComponent() {
const { drop, refresh, clear, getCachingNodes } = useAliveController();
// 删除指定页的缓存(name / cacheKey 与 pathname 一致,除非改过 getLayoutRouteCacheKey)
void drop("/explore");
// 刷新:卸载后下次进入重新挂载
void refresh("/explore");
// 清空全部缓存
void clear();
// 查看当前缓存列表
console.log(getCachingNodes());
}
常见场景:
| 场景 | 建议 |
|---|---|
| 用户退出登录 | clear() |
| 某列表页强制刷新 | refresh("/explore") |
| 发布内容后探索页要最新 | useActivate 里 refetch,或 refresh |
9. 配置检查清单
新增或调整缓存行为时,可按此核对:
- 路由是否在
LayoutRoot.children内? isLayoutRouteCacheable对该 pathname 是否为true?getLayoutRouteCacheKey是否需要自定义合并 key?- 重页面是否应排除缓存(内存占用)?
- 切回时是否需要
useActivate拉新数据? LAYOUT_ROUTE_CACHE_MAX是否需要调整?
10. 注意事项
-
AliveScope不能删
在src/main.tsx,且须包住RouterProvider。 -
不要在 KeepAlive 内部再嵌套 KeepAlive
也不要在 KeepAlive 子树里使用useAliveController做 LRU,容易报错或死循环。淘汰逻辑只在RouteCacheController一处维护。 -
不要开启 React StrictMode + 嵌套 Alive
当前main.tsx已关闭 StrictMode,与 react-activation 兼容有关。 -
Layout 侧栏不会因切页卸载
LayoutRoot只挂载一次;缓存的是CachedLayoutOutlet里的页面区域,不是整个 Layout。 -
query 变化不拆缓存
若业务依赖?mcRefresh=xxx等同 pathname 刷新,请在页面内监听useLocation().search,或使用useActivate/refresh,不要指望 remount。
11. 快速示例汇总
新增可缓存页面 /settings
// router/index.tsx → LayoutRoot.children
{ path: "settings", element: <Settings /> }
无需改 routeCache.ts(默认即可缓存)。
排除 /settings 不缓存
// routeCache.ts
export function isLayoutRouteCacheable(pathname: string): boolean {
if (pathname === "/settings") return false;
return pathname !== ROUTES.CREATE && !pathname.startsWith(`${ROUTES.CREATE}/`);
}
调整最多缓存 5 个页面
// routeCache.ts
export const LAYOUT_ROUTE_CACHE_MAX = 5;
12. 相关依赖
react-activation:KeepAlive 实现react-router-domv6:createBrowserRouter+LayoutRoot嵌套路由
文档与代码不一致时,以 src/router/routeCache.ts 及 src/router/index.tsx 为准。