react 19 新特性
Hooks
1. useTransation
useTransition 是React 18 引入的 hook,用于将某些状态更新标记为低优先级,使其可被高优先级更新(如用户输入)中断,从而保证界面的交互流畅性。
API 返回两个值:const [isPending, startTransition] = useTransition()。isPending 是一个布尔值,表示当前是否有过渡任务在进行中;startTransition 是一个函数,接收一个回调,回调内的状态更新会被赋予低优先级。
底层原理基于 React 18 的 Lane 优先级系统 。Lane 是一个 31 位的整数,每一位代表一个优先级等级。普通事件处理中的 setState 会被分配到 SyncLane 或 InputContinuousLane(高优先级),而 startTransition 包裹的 setState 会被分配到 TransitionLane(低优先级)。React 的 Scheduler 在调度渲染任务时,总是优先处理高优先级的 lane。
可中断渲染是 fiber 架构带来的核心能力。当 Transition 正在执行渲染(fiber 树的 diff 和构建工作正在进行),此时如果用户触发了高优先级更新(比如在搜索框中继续输入),React 会丢弃当前未完成的低优先级渲染 ,立即处理高优先级更新,等高优先级渲染完成后,再从 fiber 树的根节点重新开始低优先级渲染。用户感知到的就是:输入框的响应始终是即时的,而列表的过滤计算会"让路",不会卡住输入。
典型场景是搜索过滤:用户在输入框输入关键词,输入框自身的值更新是高优先级的(保证打字不卡顿),而根据关键词过滤大数据列表的操作放在 startTransition 中:
jsx
// 非完整代码
function APP() {
const [isPending, startTransition] = useTransition();
const updateQuantityAction = () => {
// 降低 计算任务 的优先级,优先响应用户输入
startTransition(() => {
setQuantity(savedQuantity);
});
}
return (
<div>
<span>
{isPending ? "🌀 Updating..." : `${intl.format(quantity * 9999)}`}
</span>
</div>
)
}
isPending 可以用来展示过渡状态,比如给列表区域加一个半透明遮罩或 loading 动画,让用户知道计算正在进行。

需要注意的几点:startTransition 不能用于受控输入的 value 更新,因为输入框必须同步更新才能正确显示。如果一个 transition 过程中触发了 Suspense(数据请求),React 会保持旧 UI 展示并显示 isPending,等数据返回后完成过渡渲染,不会出现空白闪烁。
和 useDeferredValue 的关系:两者底层都是通过 TransitionLane 实现的,useDeferredValue 是 useTransition 的声明式版本------你不需要手动调用 startTransition,只需要传入一个值,React 自动返回一个延迟更新的版本。useTransition 适合需要主动触发状态更新的场景,useDeferredValue 适合根据某个值派生 UI 的场景。
2.useActionState
useActionState 是 React 19 专门为表单处理 设计的新 Hook。它把表单的状态管理 、异步提交 、加载状态 、错误处理整合在一起,大幅减少样板代码。
基本用法:
js
import { useActionState } from 'react';
async function submitAction(prevState, formData) {
// prevState: 上一次返回的状态
// formData: 表单提交的数据
const result = await fetch('/api/user', { method: 'POST', body: formData });
return { success: true, data: await result.json() };
}
function Form() {
const [state, formAction, isPending] = useActionState(submitAction, { success: false });
return (
<form action={formAction}>
<input name="name" />
<button disabled={isPending}>{isPending ? '提交中...' : '提交'}</button>
{state.success && <p>提交成功!</p>}
</form>
);
}
state:当前状态,初始值为useActionState的第二个参数,之后每次 action 执行完成后会更新为 action 的返回值。formAction:传给<form action={...}>的新 action 函数,内部会调用你定义的异步submitAction,并自动管理 pending 和状态。isPending:布尔值,表示当前是否有表单提交在进行中。
底层原理:useActionState 本质上是 useReducer + useTransition 的组合,并附加了表单事件的拦截逻辑。
-
调用
formAction时,React 会将其包裹在一个startTransition中。这意味着action函数内部的所有状态更新(包括最终的setState)都会被分配为TransitionLane(低优先级),与用户输入等高优先级更新隔离。这样用户在提交表单时点击按钮或输入其他字段仍然流畅,不会被异步请求卡住。 -
当你写
<form action={formAction}>时,React **不会 **把formAction直接赋给原生form.action属性。相反,React 会:- 拦截表单的
submit事件,调用preventDefault() - 通过
new FormData(formElement)提取表单字段 - 调用
formAction(formData),并传入收集好的FormData
- 拦截表单的
-
由于
action是异步的,用户可能连续多次点击提交。React 内部维护一个更新队列 ,每次调用formAction时:-
记录当前
state的快照(通过闭包) -
当异步
action完成时,将返回值作为一个Update对象推入队列 -
React 调度器按调用顺序依次应用这些更新,而不是按网络返回顺序,从而避免竞态条件
-
-
isPending的实现 :复用useTransition的isPending,只要还有未完成的过渡任务(即startTransition的回调尚未执行完),isPending就为true。连续多次提交时,isPending会在第一个提交开始时变为true,直到最后一个提交完成才变回false。
3.useOptimistic
useOptimistic 是 React 19 引入的 Hook,用于实现乐观更新 (Optimistic UI)。它允许你在异步操作(如发送消息、点赞、删除)完成之前 就提前更新 UI,让用户感觉操作是即时的;如果后台操作失败,UI 可以自动回滚到之前的状态。典型场景:聊天应用发送消息、点赞按钮、表单提交等任何需要网络请求但希望立即反馈的地方。
2. 基本用法
jsx
import { useOptimistic, useState } from 'react';
function Component() {
const [actualState, setActualState] = useState(initialValue);
const [optimisticState, setOptimisticState] = useOptimistic(
actualState, // 真实状态
(currentState, optimisticValue) => {
// 更新函数:根据当前状态和传入的乐观值,计算出临时状态
return { ...currentState, ...optimisticValue };
}
);
// 触发乐观更新
const handleOptimisticAction = () => {
setOptimisticState({ liked: true }); // 立即改变 UI
// 然后发起真实请求...
};
return <div>{optimisticState.something}</div>;
}
返回值说明:
optimisticState:当前要渲染的状态。如果没有进行乐观更新,它等于actualState;如果调用了setOptimisticState,它会变成updateFn返回的值。setOptimisticState:触发乐观更新的函数。调用时传入一个optimisticValue,React 会立即用updateFn(actualState, optimisticValue)计算新状态并重渲染。
3. 底层原理
useOptimistic 的底层基于 React 的 Fiber 架构 和 状态覆盖机制 。每个使用了该 Hook 的组件,React 内部会同时维护 真实状态 和 乐观状态 两个槽位。当调用 setOptimisticState 时,React 不会 修改真实状态的更新队列,而是在组件的 memoizedState 上标记一个 "乐观覆盖标志" ,并保存 updateFn 和传入的 optimisticValue。在 渲染阶段 ,React 会优先检查这个标志:如果存在覆盖,则执行 state = updateFn(actualState, optimisticValue) 得到临时状态并渲染;否则渲染真实状态。这个检查发生在协调(reconciliation)之前,因此 UI 能 立即响应,无需等待异步任务。
当真实状态通过 setActualState 发生变化时(例如异步请求成功后提交最终结果),React 会重新执行 updateFn(新的真实状态, optimisticValue),由于此时真实状态已经变成预期值,计算结果与当前乐观 UI 一致,用户感知不到额外刷新。如果计算结果与真实状态 完全相等 ,React 会自动清除乐观覆盖标志。当异步请求失败需要 回滚 时,开发者只需手动将真实状态重置为旧值(例如 setActualState(oldValue)),此时 updateFn(旧的真实状态, optimisticValue) 会计算出不同的结果(如从"已点赞"变回"未点赞"),UI 就会自动回滚。注意 useOptimistic 本身不提供自动回滚 ,只负责根据当前真实状态和乐观值计算临时状态,回滚逻辑完全由开发者通过 setActualState 控制。
4. useFormStatus
useFormStatus 是 React 19 引入的 Hook,用于在表单内部的子组件 中获取当前表单的提交状态 。它解决了之前需要将 isPending 通过 props 层层传递到表单内部控件的问题,让输入框、按钮等子组件可以直接读取表单的 pending 状态,从而实现更优雅的加载态展示。
基本用法:
jsx
import { useFormStatus } from 'react';
function SubmitButton() {
const { pending, data, method, action } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '提交中...' : '提交'}
</button>
);
}
function MyForm() {
return (
<form action={submitAction}>
<input name="username" />
<SubmitButton /> {/* 内部能感知到表单的提交状态 */}
</form>
);
}
useFormStatus 返回一个对象,包含以下属性:
pending:布尔值,表示表单是否正在提交中。data:FormData对象,包含当前正在提交的表单数据(仅在提交期间有值)。method:字符串,表单的提交方法('get'或'post')。action:函数,表单的 action 函数(如果使用了actionprop)。
底层原理:
useFormStatus 的底层依赖于 React 19 新增的 表单上下文(Form Context) 。当一个 <form> 元素通过 action prop 绑定了异步提交函数时,React 会在内部创建一个 Form 状态上下文 ,并注入到该表单子树中。这个上下文会追踪当前是否有正在进行的提交任务(即 startTransition 包裹的 action 是否尚未完成)。useFormStatus 本质上就是通过 useContext 读取这个内部上下文的值。当 pending 从 false 变为 true 或反之,所有调用了 useFormStatus 的子组件都会自动重渲染。与 useActionState 返回的 isPending 不同,useFormStatus 可以在任意深度的子组件中使用,而无需通过 props 层层传递,特别适合封装通用的提交按钮或加载指示器。
典型场景 :封装一个全局可复用的提交按钮组件,自动显示 loading 文字或禁用状态;或者在表单内部的多个地方(如按钮、进度条、提示文本)分别获取 pending 状态,统一响应提交过程。注意 useFormStatus 必须在 <form> 元素的子组件中调用,否则会抛出错误。
5. Refs as Props
在 React 19 之前,函数组件默认不接收 ref 作为 prop ,因为 ref 是 React 的保留属性,与 key 类似。如果你想将父组件的 ref 传递给子组件内部的某个 DOM 元素,必须使用 forwardRef 手动转发。这导致大量样板代码,尤其是在组件库或深层嵌套场景中。
React 19 移除了这一限制:ref 现在可以像普通 prop 一样直接传递 。函数组件会自动接收 ref 参数,无需 forwardRef 包裹。
基本用法:
jsx
// React 18 及之前:必须用 forwardRef
const Button = forwardRef((props, ref) => (
<button ref={ref} {...props} />
));
// React 19:直接接收 ref 作为普通 prop
function Button({ ref, children, ...props }) {
return <button ref={ref} {...props}>{children}</button>;
}
function Parent() {
const buttonRef = useRef(null);
return <Button ref={buttonRef} onClick={() => alert('clicked')}>点击</Button>;
}
底层原理:
React 19 在组件内部对 ref 的处理逻辑发生了变化。过去,React 在解析 JSX 时会从 props 对象中过滤掉 ref 和 key,不将它们传递给组件实例。而在新版本中,ref 不再被特殊对待,它和其他 prop 一样被保留并传入组件。但这并不意味着所有组件都会自动将 ref 附加到某个 DOM 节点------你仍然需要显式地将 ref 赋给目标元素 。React 只是取消了编译时和运行时的拦截,让 ref 变得透明。同时,forwardRef 并没有被移除,如果你希望保持向后兼容或显式声明转发意图,仍然可以使用它。对于组件库作者,推荐继续使用 forwardRef 作为文档化的 API 边界;对于应用代码,直接传递 ref 可以显著减少嵌套层级。
典型场景 :任何需要获取子组件内部 DOM 节点或组件实例的地方。例如自定义输入框、模态框、滚动容器等。注意,如果你的组件需要同时支持旧版本和新版本 React,建议继续使用 forwardRef 以确保兼容性。此外,类组件不受此变化影响------它们仍然通过 this.props.ref 接收 ref,但通常不推荐这样做。
6. use
use 是 React 19 引入的新 API,用于消费 Promise 或 Context ,并且可以在条件语句、循环和回调中调用 ,打破了传统 Hooks 只能在组件顶层使用的限制。当 use 消费一个 Promise 时,它会配合 <Suspense> 实现声明式的数据获取:Promise 未完成时组件会 suspend,展示 fallback;resolve 后自动恢复渲染。
基本用法(搭配 Suspense):
jsx
import { use, Suspense } from 'react';
// 模拟异步请求
function fetchUser() {
return fetch('/api/user').then(res => res.json());
}
// 数据获取组件
function User() {
const user = use(fetchUser());
return <div>{user.name}</div>;
}
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<User />
</Suspense>
);
}
use 也可以接收 Context,效果等同于 useContext,但可以在条件语句中使用:
jsx
function ConditionalTheme({ show }) {
if (show) {
const theme = use(ThemeContext);
return <div>{theme}</div>;
}
return null;
}
底层原理:
use 不依赖 Hooks 的调用顺序(因此可以在条件中使用),而是通过 Promise 状态缓存 实现。React 内部维护一个从 Promise 对象到其状态的映射。当 use(promise) 被调用时,React 检查 Promise 的状态:如果已 resolve,直接返回值;如果 pending,则抛出该 Promise,触发最近的 <Suspense> 边界展示 fallback;如果 reject,则抛出错误给 Error Boundary。Promise 完成后 React 会重新渲染组件,use 返回最终数据。
典型场景 :在客户端组件中替代 useEffect + useState 的数据获取模式,配合 Suspense 实现更简洁的加载态管理;在条件渲染中按需读取 Context;在 Server Components 与 Client Components 之间传递 Promise,实现流式 SSR。注意 use 目前只支持 Promise 和 Context,且必须在 React 组件或 Hook 中调用。
7. Document Metadata
在 React 19 之前,管理页面的 <title>、<meta> 等文档头信息通常需要借助第三方库(如 react-helmet)或手动操作 DOM。React 19 原生支持在组件中直接渲染这些标签,React 会自动将它们提升到文档的 <head> 中,并处理重复和冲突。
基本用法:
jsx
function ProductPage({ product }) {
return (
<>
<title>{product.name} - 我的商店</title>
<meta name="description" content={product.description} />
<meta property="og:image" content={product.imageUrl} />
<h1>{product.name}</h1>
</>
);
}
无论 ProductPage 嵌套多深,<title> 和 <meta> 都会被 React 自动移动到 <head> 区域。如果多个组件同时定义了相同标签(比如多个 title),React 会基于组件树的深度决定优先级------更靠近叶子的组件覆盖祖先组件。
底层原理:
React 19 在渲染阶段会收集所有标记为"可提升"的元素(如 <title>、<meta>、<link> 等),并在 commit 阶段将它们插入或更新到真实的 <head> 中。React 维护了一个全局的元数据映射,根据组件的深度和顺序自动处理覆盖、去重和移除。当组件卸载时,对应的元数据也会被清除。
典型场景 :在页面组件中动态设置 SEO 相关的标题和描述;在路由切换时自动更新浏览器标签页标题;在深嵌组件(如图片模态框)中临时覆盖父页面的 meta 信息。注意 React 19 仅支持特定类型的元数据标签(title、meta、link、style 和 script),其他元素不会被提升。
8. React Compiler
React Compiler(曾用名 React Forget)是 React 19 带来的构建时优化工具 ,它会在编译阶段自动分析你的组件代码,并注入必要的记忆化逻辑,让你彻底告别手动编写 useMemo、useCallback 和 React.memo。
在 React 18 及之前,只要父组件更新,React 就会重新渲染该组件及其所有子组件------除非你手动用 useMemo、useCallback 或 React.memo 进行优化。这不仅增加了代码量,还容易出错,即使是有经验的开发者,也很难保证所有该优化的地方都优化到位。React Compiler 像一个聪明的助手,在编译时自动分析你的组件代码,判断哪些值、函数和 JSX 表达式需要被记忆化,并在合适的地方自动插入优化逻辑。
基本用法(对比前后):
jsx
// React 18 写法:需要手动记忆化
import { useState, useMemo, useCallback } from 'react';
function expensiveCalculation(data) { /*执行昂贵计算...*/ }
function MyComponent({ data }) {
const [count, setCount] = useState(0);
const calculatedValue = useMemo(() => expensiveCalculation(data), [data]); // useMomo
const handleClick = useCallback(() => setCount(c => c + 1), []); // useCallback
return (
<div>
<p>计算结果: {calculatedValue}</p>
<button onClick={handleClick}>点击 {count}</button>
</div>
);
}
export default React.memo(MyComponent);
jsx
// React 19 + Compiler:无需任何优化 Hook,编译器自动处理
function MyComponent({ data }) {
const [count, setCount] = useState(0);
const calculatedValue = expensiveCalculation(data);
const handleClick = () => setCount(c => c + 1);
return (
<div>
<p>计算结果: {calculatedValue}</p>
<button onClick={handleClick}>点击 {count}</button>
</div>
);
}
底层原理:
React Compiler 是一个 Babel 插件,在构建时对代码进行静态分析。它首先将组件代码解析成高级中间表示(HIR,High-Level Intermediate Representation) ,这是一种基于控制流图(CFG)构建的内部表示,比传统的抽象语法树(AST)更能描述代码的执行逻辑。基于 HIR,编译器进行单静态赋值(SSA)转换、类型推断、效应分析、响应式分析和作用域发现 等多轮分析,追踪每个变量的依赖关系和变动范围。基于分析结果,编译器识别出哪些值在组件重渲染时会发生变化,自动注入 useMemo、useCallback 等记忆化逻辑。如果检测到代码违反了 React 规则(如条件性地调用 Hook),编译器会安全地跳过那些组件,继续优化其他部分,不会导致整个构建失败。
典型场景 :任何存在组件重渲染性能瓶颈的 React 应用,尤其是大型列表过滤、复杂计算、深层组件树等场景,效果显著。注意 React Compiler 是一个可选工具,支持 React 17+,但建议配合 React 19 使用以获得最佳体验