全新popiart网站 react项目重构
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

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. 默认哪些路由会缓存

会缓存

挂在 LayoutRootchildren 里、且 isLayoutRouteCacheable(pathname) 返回 true 的路由。

当前默认缓存:

路径 页面
/explore 探索
/characters 角色
/characters-create 角色创建
/subscribe 订阅
/profile 个人中心
/legal 法律条款
/content/page 内容页

不会缓存

  1. 不在 LayoutRoot 下的路由(根本不会经过 CachedLayoutOutlet
    例如:/index/404

  2. isLayoutRouteCacheable 里被排除的路由
    当前排除:/create 及所有 /create/* 子路由


3. 如何让新页面加入缓存

需要同时满足两点。

步骤 A:路由挂到 LayoutRoot

src/router/index.tsxLayoutRootchildren 中增加路由:

{
  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. 注意事项

  1. AliveScope 不能删
    src/main.tsx,且须包住 RouterProvider

  2. 不要在 KeepAlive 内部再嵌套 KeepAlive
    也不要在 KeepAlive 子树里使用 useAliveController 做 LRU,容易报错或死循环。淘汰逻辑只在 RouteCacheController 一处维护。

  3. 不要开启 React StrictMode + 嵌套 Alive
    当前 main.tsx 已关闭 StrictMode,与 react-activation 兼容有关。

  4. Layout 侧栏不会因切页卸载
    LayoutRoot 只挂载一次;缓存的是 CachedLayoutOutlet 里的页面区域,不是整个 Layout。

  5. 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-dom v6:createBrowserRouter + LayoutRoot 嵌套路由

文档与代码不一致时,以 src/router/routeCache.tssrc/router/index.tsx 为准。