ice.js 实战:基于 React 18 `<Activity />` 的 KeepAlive 页面保活示例(with-keep-alive-react)

📅 发布时间:2026/9/21 1:35:32
ice.js 实战:基于 React 18 `<Activity />` 的 KeepAlive 页面保活示例(with-keep-alive-react)
前端Web框架SSR前端构建插件系统微前端跨平台【免费下载链接】ice ice.js: The Progressive App Framework Based On React基于 React 的渐进式应用框架项目地址https://gitcode.com/gh_mirrors/ice1/ice点击查看免费下载导读KeepAlive页面保活是现代前端应用中非常实用的能力它能让用户从 A 页面切到 B 页面时A 页面的滚动位置、表单输入、组件内部状态等不因卸载而丢失返回时瞬间恢复。ice.js 在examples/with-keep-alive-react示例中演示了一种基于 React 18 实验性Activity /API 的 KeepAlive 实现方案。本文以该示例为骨架完整解析其页面结构、核心 APIKeepAliveOutlet、useActive、底层实现原理以及基于 yalc 的本地调试流程帮助你快速在自己的 ice.js 项目中落地页面保活能力。示例定位Experimental keep-alive with React 18Activity /仓库中的examples/with-keep-alive-react是一个独立可运行的 ice.js 应用其 README 开宗明义地给出了它的定位Experimental keep-alive with React 18Activity /.也就是说这是一个实验性示例用来验证 ice.js 运行时对 React 18 新引入的Activity /能力即页面/组件级可见性控制原语的封装。它没有引入任何第三方 KeepAlive 库而是直接复用了 React 实验版本内置的unstable_Activity并把哪些路由出口要被保活、保活多少个、保活哪些路径这类策略问题封装成了 ice.js 运行时自带的KeepAliveOutlet组件见 KeepAliveOutlet.tsx。从示例的package.json可以看到该示例强依赖 React 实验版本{ dependencies: { react: 0.0.0-experimental-0cdfef19b-20231211, react-dom: 0.0.0-experimental-0cdfef19b-20231211 }, resolutions: { react: 0.0.0-experimental-0cdfef19b-20231211, react-dom: 0.0.0-experimental-0cdfef19b-20231211 } }这里通过resolutions强制锁定 React 版本确保实验性 API 行为一致。这是运行该示例前需要知晓的一个前提KeepAlive 示例需要 experimental 版本的 React。示例目录结构与页面拓扑examples/with-keep-alive-react/ ├── src/ │ ├── components/ │ │ └── Counter.tsx # 保活效果演示用的计数器组件 │ ├── pages/ │ │ ├── index.tsx # 首页 /含 Counter 与跳转链接 │ │ ├── layout.tsx # 根布局渲染 KeepAliveOutlet / │ │ └── about/ │ │ ├── index.tsx # 子页面 /about │ │ ├── layout.tsx # 子路由布局渲染普通 Outlet / │ │ └── me.tsx # 子页面 /about/me含 input │ ├── app.ts # defineAppConfig 入口 │ └── document.tsx # HTML 模板 ├── ice.config.mts # defineConfig 空配置 ├── package.json └── tsconfig.json页面拓扑关系如下根布局src/pages/layout.tsx渲染KeepAliveOutlet /所有被保活的页面都在它内部首页/index.tsx挂载一个Counter计数器组件/aboutabout/index.tsx同样挂载一个Counter/about/meabout/me.tsx包含一个input /输入框/about自身还有一个布局about/layout.tsx内部使用普通的Outlet /。这样一个拓扑故意制造了多种典型场景组件级状态Counter、表单输入input、嵌套路由布局layout Outlet从而可以完整地验证保活效果。核心代码逐层拆解1. 根布局用KeepAliveOutlet替换普通Outlet普通 ice.js 应用的路由出口通常写成import { Outlet } from ice;而保活示例的根布局layout.tsx换成了import { KeepAliveOutlet } from ice; export default function Layout() { return ( h1Im Keep Alive/h1 KeepAliveOutlet / / ); }这是整个保活能力接入的唯一入口改动把Outlet /换成KeepAliveOutlet /ice.js 运行时便会接管路由出口的渲染与缓存。2. 页面组件保持普通写法无需任何侵入式改造被保活的页面组件本身不需要任何额外代码。首页index.tsximport { Link } from ice; import Counter from /components/Counter; export default function Home() { return ( main h2Home/h2 Counter / Link to/aboutAbout/Link /main ); } export function pageConfig() { return { title: Home, }; }/aboutabout/index.tsx与/about/meabout/me.tsx同样保持常规写法。这种零侵入正是 KeepAlive 方案易用性的关键业务代码无需感知保活的存在只需要在布局层替换一个组件。Counter组件Counter.tsx用useState保存计数import { useState } from react; export default function Counter() { const [count, setCount] useState(0); return ( div count: {count} button onClick{() setCount((count) count 1)}add/button /div ); }3. 嵌套路由子布局继续使用普通Outlet注意/about下的子布局about/layout.tsx仍然使用普通Outletimport { Outlet } from ice; export default function AboutLayout() { return ( h2About Layout /h2 Outlet / / ); }这说明 KeepAlive 的粒度在示例中被设置在顶层路由出口保活的对象是/、/about、/about/me这些顶层路由条目对应的出口而子路由内部继续用原生路由机制渲染。4. 应用入口与配置应用入口app.ts是一个标准的defineAppConfigimport { defineAppConfig } from ice; export default defineAppConfig(() ({}));构建配置ice.config.mts为空配置import { defineConfig } from ice/app; export default defineConfig(() ({}));即KeepAlive 能力不需要任何构建期配置纯运行时提供。KeepAliveOutlet源码实现解读KeepAliveOutlet由 ice.js 运行时包ice/runtime提供实现在 packages/runtime/src/KeepAliveOutlet.tsx并从 packages/runtime/src/index.ts 统一对外导出useActive、KeepAliveOutlet。// ts-ignore const Activity React.unstable_Activity || ActivityComponent;这里有一个重要的降级策略优先使用 React 实验版本提供的React.unstable_Activity如果当前 React 版本不支持即非实验版则回退到运行时内置的ActivityComponent见 Activity.tsx。如果两者都不可用组件会直接抛出明确错误if (!Activity) { throw new Error(KeepAliveOutlet / now requires react experimental version. Please install it first.); }保活队列管理组件内部用useState维护一个出口队列队列元素包含outlet、key、pathname三个字段interface ActivityItem { outlet: React.ReactElement | null; key: string; pathname: string; }在useEffect中每当路由变化location.pathname/location.key变化时将当前的useOutlet()结果追加进队列并用.slice(-outletLimit)保证队列长度不超过上限从而实现只保留最近 N 个页面的 LRU 式裁剪const OUTLET_LIMIT 5; ... const outletLimit props.limit || OUTLET_LIMIT;默认保活上限为5 个出口OUTLET_LIMIT 5可通过limitprop 覆盖。支持 propsKeepAliveOutlet支持两个可选 props定义见 KeepAliveOutlet.tsxProp类型默认值说明limitnumber5保活的出口outlet数量上限超出后最旧的出口会被淘汰pathsstring[]未配置全部保活仅对指定路径启用保活未匹配的路径不缓存paths的实现逻辑是在追加新出口之前先对现有队列执行过滤currentOutlets.filter(o keepAlivePaths.includes(o.pathname))从而只保留命中列表的页面。这适用于只想缓存首页、详情页不缓存表单页之类的精细化控制场景。仓库中另一个 KeepAlive 示例 examples/with-keep-alive/src/pages/layout.tsx 就同时用到了这两个参数KeepAliveOutlet limit{2} paths{[/home]} /SSR 水合兼容源码中对 SSR 场景做了专门处理用一个useRef保存首个出口首屏渲染时的outlet在outlets为空时直接渲染outletRef.current避免客户端水合hydration时额外触发setOutlets造成重复渲染const outletRef useRef({ key: location.key, pathname: location.pathname, outlet, }); ... const renderOutlets outlets.length 0 ? [outletRef.current] : outlets;这也是为什么示例同时保留了 document.tsx 这样的 SSR 文档模板——KeepAlive 在服务端渲染/水合链路中同样可用。可见性切换modevisible | hidden队列中的每个出口都会被包一层Activity并根据当前路由决定可见性Activity key{o.key} mode{location.pathname o.pathname ? visible : hidden} {o.outlet} /Activity当前路径的出口以visible模式渲染其余保活页面以hidden模式渲染。Activity与useActive保活页面的可见性语义当 React 实验版本不可用时运行时内置的降级实现位于 packages/runtime/src/Activity.tsxexport default function Activity({ mode, children }: ActivityProps) { const active mode visible; return ( ActivityProvider value{{ active }} {/* Additional wrapper for hidden elements */} div style{{ display: active ? block : none }} {children} /div /ActivityProvider ); }可以看到降级方案的核心是被隐藏的页面并不卸载而是通过display: none保持在 DOM 中因此组件的useState等内部状态得以保留——这正是 KeepAlive 的本质。同时Activity通过 React Context 暴露active状态并对外提供useActive钩子同样从 index.ts 导出export const useActive () { const data React.useContext(Context); return data?.active; };业务组件可以在保活页面内部这样使用它感知自己是否处于前台可见状态import { useActive } from ice; function MyPage() { const active useActive(); // active true 表示当前页面可见false 表示被保活隐藏 }这在页面被隐藏时暂停轮询/动画、回到前台时恢复这类场景非常实用。基于 yalc 的本地调试流程该示例的 README 给出了一套完整的本地调试流程。由于示例依赖的是仓库内正在开发的ice/app与ice/runtime而非 npm 上发布的稳定版本因此需要先用yalc把这两个包发布到本地仓库再在示例中安装。第 1 步将核心包发布到 yalc 仓库$ cd packages/ice yalc publish --push $ cd packages/runtime yalc publish --pushyalc publish --push会将当前目录下的包发布到本地 yalc 仓库并推送push到所有已添加该包的本地项目中顺序上先packages/ice构建工具链ice/app再packages/runtime运行时ice/runtime与依赖方向一致。第 2 步进入示例目录并添加本地依赖$ cd examples/with-keep-alive $ yalc add ice/app ice/runtimeyalc add会把本地仓库中的ice/app、ice/runtime链接进示例项目的node_modules。这样示例运行时使用的是当前仓库源码构建出的最新包改动packages/下的源码后只需重新yalc publish --push即可热同步。说明examples/with-keep-alive-react与examples/with-keep-alive是仓库内两个并列的 KeepAlive 示例前者面向 React 18Activity /实验 API后者展示limit/paths参数的用法README 中的调试命令以with-keep-alive目录为例路径按需替换即可。第 3 步安装依赖并启动$ yarn install $ npm run startyarn install负责补齐示例自身声明的依赖包括实验版 React随后npm run start对应 package.json 中的ice start启动本地开发服务器。第 4 步验证 KeepAlive 效果启动后打开首页可以这样验证在首页点击add按钮把Counter计数加到一个非零值点击About链接跳到/about再把该页的计数器加几下在/about/me的input /中输入一些文字依次返回/about、/观察计数器和输入框内容是否完整保留。如果一切正常会发现切换页面后状态不丢失——这正是KeepAliveOutlet /生效的表现。常见问题与注意事项必须使用 experimental ReactKeepAliveOutlet依赖React.unstable_Activity。如果 React 版本不满足会抛出KeepAliveOutlet / now requires react experimental version. Please install it first.错误。示例通过package.json的resolutions字段锁定了实验版本0.0.0-experimental-0cdfef19b-20231211。保活数量有上限默认最多保活 5 个出口超出后最旧的会被淘汰slice(-outletLimit)。需要更多时通过limitprop 调整。按路径精细化控制通过pathsprop 指定需要保活的路径白名单未命中路径的页面不会被缓存。页面可见性感知被保活的页面处于hidden状态时并未卸载display: none如有需要可用useActive()判断当前是否可见据此暂停/恢复定时器等副作用。实验性功能该示例在 README 中明确标注为 Experimental其底层依赖 React 实验性Activity /能力API 形态在未来版本中可能演进生产环境接入前请评估稳定性。总结examples/with-keep-alive-react展示了 ice.js 运行时对 React 18Activity /保活能力的完整封装链路业务侧只需在布局中把Outlet /替换为KeepAliveOutlet /即可获得页面状态保留能力运行时侧通过出口队列管理、limit/paths策略参数、SSR 水合兼容与useActive可见性感知把这套能力打磨成了开箱即用的 API。配合 README 中的 yalc 调试流程你可以在本地快速复现、修改并验证 KeepAlive 行为为生产环境中的页面保活方案选型提供直接参考。赞分享前端Web框架SSR前端构建插件系统微前端跨平台【免费下载链接】ice ice.js: The Progressive App Framework Based On React基于 React 的渐进式应用框架项目地址https://gitcode.com/gh_mirrors/ice1/ice点击查看免费下载相关推荐ice.js 组件缓存Keep Alive实战指南用 KeepAliveOutlet 与 Activity 缓存组件状态ice.js 组件缓存Keep Alive实战指南用 KeepAliveOutlet 与 Activity 缓存组件状态 导读 ice.js 作为基于 R前端Web框架SSR前端构建插件系统微前端跨平台gh_mirrors/erro/errors源码解析如何优雅记录错误上下文与调用栈gh_mirrors/erro/errors源码解析如何优雅记录错误上下文与调用栈 在Go语言开发中错误处理是保证程序健壮性的关键环节。 gh_mirroReact Keep-Alive 使用教程React Keep Alive 使用教程 项目介绍 react keep alive 是一个用于 React 应用的库旨在提供类似于 Vue 的 keep创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考