讲解
React 19(2024 年 12 月稳定)的主题是「把异步变简单」。最核心的新概念是 Action:一个传给表单的异步函数,React 自动管理它的 pending 状态、错误和乐观更新。配合三个新 Hook:
useActionState——表单状态机:const [error, submitAction, isPending] = useActionState(fn, null),fn 接收 (上一个状态, 表单数据 FormData),返回的新状态自动更新,提交期间 isPending 为真。以前要手写三四个 useState 的「提交中/成功/失败」逻辑,现在一个 Hook 收编。
useOptimistic——乐观更新:const [optimisticTodos, addOptimistic] = useOptimistic(todos),提交时立刻把新项显示出来,请求失败自动回滚到真实状态。点赞、发送消息这类「用户不想等」的交互,体验从「转圈一秒」变成「瞬时响应」。
use——可以在渲染期间读 Promise 和 Context 的新 Hook(它是唯一能放进条件里的 Hook):const data = use(promise),配合 Suspense 显示加载态。它把「Promise 结果」变成了可以同步书写的值,是 React 服务端组件和客户端数据加载的统一接口。
其他值得知道的:ref 现在可以直接作为普通 prop 传递(forwardRef 退出历史舞台);useFormStatus 让深层子组件(比如提交按钮)不用 props 就能拿到所在表单的提交状态;文档元数据(title/meta)可以直接在组件里渲染。升级建议:新项目直接用 19 的写法,老项目这些特性都是增量式采纳,不用重写。
示例
模拟 useActionState 的表单状态机和 useOptimistic 的回滚(在本教程构建时被真实执行):
import assert from 'node:assert/strict';
// 迷你 useActionState:pending 自动管理,返回值成为新状态
async function simulateAction(actionFn, formData, failTimes = 0) {
let state = null;
let pending = false;
let attempts = 0;
async function dispatch(data) {
pending = true;
attempts++;
try {
state = await actionFn(state, data, attempts <= failTimes);
} finally {
pending = false;
}
return { state, pending };
}
pending = true;
const result = await dispatch(formData);
return result;
}
// 注册 action:返回错误信息或 null(成功)
async function register(prev, formData, shouldFail) {
if (shouldFail) throw new Error('network');
if (!formData.name) return '姓名不能为空';
if (formData.name.length < 2) return '姓名至少 2 个字';
return null;
}
const bad = await simulateAction(register, { name: '小' });
assert.strictEqual(bad.state, '姓名至少 2 个字');
assert.strictEqual(bad.pending, false); // 结束后 pending 自动复位
const good = await simulateAction(register, { name: '小明' });
assert.strictEqual(good.state, null);
// 迷你 useOptimistic:乐观展示,失败回滚
function createOptimistic(realState) {
let optimistic = realState;
return {
get value() {
return optimistic;
},
add(item, confirm) {
optimistic = [...realState, item]; // 立刻显示
return confirm()
.then(() => {
realState = [...realState, item]; // 成功:写进真实状态
optimistic = realState;
})
.catch(() => {
optimistic = realState; // 失败:回滚
});
},
};
}
const likes = createOptimistic([]);
await likes.add('小明赞了', () => Promise.reject(new Error('网络抖动')));
assert.deepStrictEqual(likes.value, []); // 自动回滚,界面回到真实状态
await likes.add('小红赞了', () => Promise.resolve());
assert.deepStrictEqual(likes.value, ['小红赞了']);
console.log('表单校验失败状态:', bad.state, '| pending 自动复位:', bad.pending === false);
console.log('乐观更新: 失败自动回滚,成功后真实状态:', likes.value.join('、'));
React 19 的真实写法:
import { useActionState } from 'react';
function SignupForm() {
const [error, submitAction, isPending] = useActionState(async (prev, formData) => {
const name = formData.get('name');
if (name.length < 2) return '姓名至少 2 个字';
await fetch('/api/signup', { method: 'POST', body: formData });
return null; // 成功
}, null);
return (
<form action={submitAction}>
<input name="name" placeholder="姓名" />
{error && <em>{error}</em>}
<button disabled={isPending}>{isPending ? '提交中…' : '注册'}</button>
</form>
);
}
常见坑
- 老代码里继续 forwardRef:React 19 里 ref 是普通 prop,新代码直接
function Input({ ref, ... })接收。 - use() 当普通 Hook 乱用:use 可以进条件,但读的 Promise 应该是稳定引用(每次渲染新建 Promise 会无限挂起)。
- Action 里手动管 pending:useActionState 已自动管理 isPending,再写 useState 是重复劳动。
- 乐观更新不做失败处理:useOptimistic 的回滚是自动的,但你要设计「失败后用户能感知」的提示。
- 盲目升级老项目:React 19 对水合错误信息等有破坏性变更,升级前看官方升级指南并跑全测试。
小结
React 19 = Actions(useActionState 管表单状态机)+ useOptimistic(乐观更新自动回滚)+ use()(渲染期读 Promise);ref 变普通 prop。下一章看异步 UI 的编排:Suspense。