用 React 19.2 Activity 保留页面状态前,先弄清 hidden 模式做了什么

多页签后台有一个反复出现的需求:用户从编辑页切到列表页,再切回来时,表单输入、滚动位置和局部筛选不能丢。最直接的写法是条件渲染:

jsx 复制代码
{activeTab === "editor" && <Editor />}

问题也很直接。条件变成 false 后,Editor 被卸载,组件内部状态随之消失。把所有状态提升到全局可以解决一部分问题,却会让临时 UI 状态进入业务 store。用 CSS 把节点隐藏则会保留状态,但副作用、订阅和更新仍可能继续运行。

React 19.2 提供了 <Activity />。它在当前版本有 visible 和 hidden 两种模式:

jsx 复制代码
import { Activity } from "react";

function Workspace({ activeTab }) {
  return (
    <>
      <Activity mode={activeTab === "editor" ? "visible" : "hidden"}>
        <Editor />
      </Activity>
      <Activity mode={activeTab === "list" ? "visible" : "hidden"}>
        <ResultList />
      </Activity>
    </>
  );
}

根据 React 官方说明,hidden 会隐藏子树、卸载 Effect,并把该子树的更新推迟到 React 没有更紧急工作的时候。切回 visible 后,内容重新显示,Effect 再次挂载,更新恢复正常优先级。它和"组件继续在后台照常运行,只是看不见"并不是一回事。

这个区别决定了 Activity 适合什么。编辑器、复杂筛选面板、可返回的路由页面,通常希望保留 React 状态和已经构建的 UI,同时不让隐藏页面的订阅长期占用资源。Activity 正好位于卸载和 CSS 隐藏之间。它也可以提前渲染用户很可能访问的下一页,让数据、样式和图片在后台准备,但不抢占眼前交互。

第一类迁移风险来自 Effect。假设组件通过 Effect 订阅 WebSocket:

jsx 复制代码
useEffect(() => {
  const unsubscribe = socket.subscribe(roomId, onMessage);
  return unsubscribe;
}, [roomId]);

进入 hidden 后,这个订阅会清理。再次显示时会重新订阅。如果业务要求页面隐藏期间仍持续接收并缓存消息,就不能把数据生命周期完全交给这个页面 Effect。更合理的做法是把连接和消息缓存放到页面之外的服务层,页面 Effect 只负责展示层订阅。Activity 会迫使我们分清"界面不可见时应该暂停的工作"和"业务上必须持续的工作"。

第二类风险是误把 Activity 当成无限缓存。保留十几个大型页面的状态和节点仍会占用内存。React 可以降低隐藏更新的优先级,却不会替应用决定何时彻底释放页面。实际项目最好给页签设置上限,区分临时切换和长期离开。最近使用的少量页面放进 Activity,超过数量后真正卸载,往往比把所有历史页面永久隐藏更稳。

第三类风险是没有测量就预渲染。提前渲染下一页能缩短切换等待,但也会产生请求、解析响应、创建对象和图片解码。如果"下一页"预测不准,后台工作只是在消耗流量。可以先记录页面转移概率,只对高概率路径预渲染,并在慢网、低性能设备和节流模式下关闭。React 19.2 还提供 Performance Tracks,可以在 Chrome DevTools 的性能记录中观察调度优先级、渲染和 Effect 工作,适合判断隐藏子树是否挤占了可见交互。

迁移时不建议一口气改路由层。先挑一个状态丢失最明显的双页签场景,记录三个指标:切换后的可交互时间、隐藏期间的网络与 CPU 活动、往返切换后的内存变化。然后检查所有 Effect 的清理与重建是否符合业务预期。若组件依赖"只在首次挂载执行一次"的副作用,还要把这类隐含假设改掉,因为可见性切换会让 Effect 再次挂载。

Activity 也不能替代服务端状态缓存。查询结果、乐观更新和失效策略仍应由数据层处理。它保留的是 React 子树及其界面状态,并调整隐藏工作的生命周期和优先级。把这条边界守住,组件不会因为"想保留一个输入框"而背上整套后台任务。

我更愿意把 Activity 看成一种页面生命周期工具。它解决的不是"怎么把 DOM 藏起来",而是用户暂时离开一个界面时,哪些状态应保留,哪些副作用应暂停,哪些数据应移到更长寿命的层。先回答这三个问题,再替换条件渲染,代码会清楚很多。

相关推荐
落魄大学生之流水线上谋生计10 小时前
Remotion:用 React 写视频,让代码成为剪辑师
react.js
光影少年1 天前
Fabric渲染流程
前端·react native·react.js
光影少年1 天前
RN启动流程(bundle加载→Bridge初始化→首屏渲染)
前端·react native·react.js
卡布鲁7 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
用户298698530147 天前
在 React 中用 JavaScript 将 Word 转换为文本
前端·javascript·react.js
狗狗狗狗狗乐啊8 天前
搭一个 AI 对话工作台 AChat:从 0 到可用的完整记录(一)
python·react.js·ai编程
晓得迷路了8 天前
栗子前端技术周刊第 147 期 - React Router 8.4、Vite 云服务攻击、pnpm 12.4...
前端·javascript·react.js
神秘的猪头9 天前
React 脱围机制(二):重新理解 useEffect——它不是生命周期,而是同步外部系统
react.js
卡布鲁9 天前
React useMemo 与 useCallback 完全指南:原理、场景与避坑
前端·react.js
右耳朵猫AI9 天前
Web前端周刊2026W38 | React 19.3 发布、StyleX 深潜、jsdom 30.1 提速
前端·javascript·react.js·typescript·node.js