写 useEffect
的时候,依赖数组总让人头疼------函数放进去吧,effect 频繁重跑;不放进去吧,又拿到陈旧的闭包值。Tab 切换想保留状态,用 CSS display: none
又感觉绕了个弯。
React 19.2 带来的 <Activity />
和 useEffectEvent
就是专门解决这些问题的。
先抛几个问题,看看你是不是也曾被它们困扰:
- 写
useEffect
时,是不是总在为依赖数组烦恼?为了避免重复执行,把函数提到外面,结果又遇到了闭包陷阱。 - 标签页(Tabs)切换时,如何能在保留组件状态的同时,优雅地隐藏和显示内容?用 CSS
display: none
总感觉不够"React"。 - 服务器渲染(SSR)的页面,如果组件嵌套得深,是不是会看到页面一块一块地加载,体验不太好?
useEffect
里的事件监听函数(比如onClick
),是不是必须放进依赖数组里,导致 effect 频繁重新执行?(这个最容易搞混)
<Activity />
API:声明式管理组件状态
之前实现 Tab 切换保留状态,通常是用 CSS 控制 display
或 visibility
。能用,但总感觉是在"绕路"。
<Activity />
组件提供了一种声明式的、更符合 React 思想的解决方案。
核心功能
<Activity />
通过 mode
prop 控制子组件的活动状态:
mode="visible"
- 挂载并显示mode="hidden"
- 暂停渲染,但状态和 DOM 树保留,不会卸载
适用场景:模态框、Tab、后台预渲染的 UI。
实战案例:带状态的 Tabs 组件
常见场景:每个 Tab 都有自己的内部状态(比如计数器),切换时要保留。
jsx
import { useState } from 'react';
import { Activity } from 'react'; // 从 react 导入
function CounterTab({ title }) {
const [count, setCount] = useState(0);
console.log(`${title} rendered`);
return (
<div>
<h3>{title}</h3>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
export default function App() {
const [activeTab, setActiveTab] = useState('A');
return (
<div>
<nav>
<button onClick={() => setActiveTab('A')}>Tab A</button>
<button onClick={() => setActiveTab('B')}>Tab B</button>
</nav>
<hr />
<Activity mode={activeTab === 'A' ? 'visible' : 'hidden'}>
<CounterTab title="Tab A" />
</Activity>
<Activity mode={activeTab === 'B' ? 'visible' : 'hidden'}>
<CounterTab title="Tab B" />
</Activity>
</div>
);
}
切换 Tab 时会发现:
- 每个 Tab 的
console.log
只在首次渲染时打印 - 切换回来后,
count
状态完美保留
比 CSS display: none
优雅,由 React 调度器管理,未来可能结合 Offscreen API 带来更多性能优势。
useEffectEvent
:解决依赖数组难题
useEffect
的依赖数组是核心,也是 Bug 高发区。想在 effect 里调用总能获取最新 props/state 的函数,又不想因函数引用变化导致 effect 重跑,这个矛盾困扰了很多人。
useEffectEvent
就是来解决这个问题的。
之前的痛点
经典问题:记录页面访问的 useEffect
,依赖 user
对象。
jsx
function Page({ user, url }) {
useEffect(() => {
// 问题:如果 user 对象在每次渲染时都是新对象,
// 这个 effect 会在每次 user 变化时都重新执行,
// 即使我们只关心 url 变化时才记录日志。
logVisit(user, url);
}, [user, url]); // 依赖数组很烦人
}
为了避免 user
变化导致 effect 重跑,把它从依赖数组去掉,就会产生"陈旧闭包"------logVisit
捕获的是第一次渲染的 user
,后续更新拿不到最新值。
useEffectEvent
的解决方案
useEffectEvent
可以将一个函数"提升"到 React 的管理体系中,让它不参与到 useEffect
的依赖追踪里,但总能访问到最新的 props 和 state。
jsx
import { useEffect, useEffectEvent } from 'react';
function Page({ user, url }) {
// 1. 用 useEffectEvent 包裹你的事件函数
const onVisit = useEffectEvent(visitedUrl => {
// 这里的 user 总能读到最新的值
logVisit(user, visitedUrl);
});
// 2. 在 useEffect 中调用它
useEffect(() => {
// onVisit 函数的引用是稳定的,不需要加入依赖数组
// 现在这个 effect 只会在 url 变化时执行
onVisit(url);
}, [url]); // 依赖数组清爽多了!
}
核心要点 :useEffectEvent
分离了"响应式逻辑"(应该触发 Effect 的,如 url
)和"非响应式逻辑"(不应触发的,如 user
),让 useEffect
的意图更清晰。
SSR 性能优化:Suspense 边界批处理
SSR 时,如果页面有多个 <Suspense>
边界,19.2 之前会为每个边界都发送一个 fallback HTML,然后再发送完整内容,导致网络请求碎片化。
19.2 引入批处理机制:React 在服务器上稍等片刻,将多个准备就绪的 Suspense 边界内容合并到同一个 HTML 响应,减少网络往返,提升初始加载性能。
这个优化自动开启,无需配置。
其他更新
cacheSignal
- 专为 Server Components 设计,当cache()
数据过期时提供AbortSignal
,可中断异步操作避免资源浪费- 部分预渲染 (PPR) - 预渲染静态"外壳",然后动态填充内容,19.2 完善了 API
- DevTools 性能追踪 - Chrome DevTools Performance 面板新增 React 专属轨道,可视化组件挂载、更新和 Effect 执行
如何升级?
bash
# 使用 npm
npm install react@latest react-dom@latest
# 使用 pnpm
pnpm update react --latest
pnpm update react-dom --latest
总结
React 19.2 核心要点:
实用层面:
<Activity />
- 状态保留场景的官方解决方案useEffectEvent
- 重构复杂useEffect
的利器,提升代码可读性和稳定性- SSR 性能自动优化,无需配置