认识 React 19
Actions:革命性的异步状态管理
告别手动状态管理的恶梦
在 React 19 之前,处理表单提交和异步操作一直是一个痛点。我们需要手动管理加载状态、错误状态和乐观更新,代码冗长且容易出错。
React 18 的传统做法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function UpdateProfile() {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const handleSubmit = async () => {
setIsPending(true);
setError(null);
try {
const result = await updateUserProfile(name);
if (result.error) {
setError(result.error);
return;
}
// 处理成功逻辑
redirect("/profile");
} catch (err) {
setError(err.message);
} finally {
setIsPending(false);
}
};
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
{isPending ? "更新中..." : "更新"}
</button>
{error && <p>{error}</p>}
</div>
);
}
React 19 的 Actions 方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function UpdateProfile() {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const error = await updateUserProfile(formData.get("name"));
if (error) {
return error;
}
redirect("/profile");
return null;
},
null
);
return (
<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>
{isPending ? "更新中..." : "更新"}
</button>
{error && <p>{error}</p>}
</form>
);
}
新的 Hooks
useActionState
React 19 引入了 useActionState hook,它接受一个 Action 函数并返回一个包装的 Action 以及相关状态。
1
2
3
4
5
6
7
8
9
10
11
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
// 异步操作
const result = await apiCall(formData);
if (result.error) {
return result.error; // 返回错误状态
}
return null; // 成功状态
},
null // 初始状态
);
useActionState 返回的第一个值是当前 action 的状态,可以用来表示错误信息、成功消息或更复杂的状态对象。
useOptimistic
useOptimistic
hook 让你可以在异步操作进行时立即显示乐观的结果。
它在内部其实同时为你管理着两个状态。
- 真实状态 (Real State):这是最可靠、最权威的状态,通常由服务器提供,通过 props 传递给你的组件。
- 乐观状态 (Optimistic State):这是一个临时的、“希望”它能成真的状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function TodoList({ todos, addTodo }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, newTodo]
);
const submitAction = async (formData) => {
const newTodo = { id: Date.now(), text: formData.get("todo") };
addOptimisticTodo(newTodo);
// 实际的异步操作
await addTodo(newTodo);
};
return (
<div>
{optimisticTodos.map(todo => (
<div key={todo.id}>{todo.text}</div>
))}
<form action={submitAction}>
<input name="todo" />
<button type="submit">添加</button>
</form>
</div>
);
}
useFormStatus
useFormStatus
允许设计系统组件访问父表单的状态信息。
1
2
3
4
5
6
7
8
9
10
11
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "提交中..." : "提交"}
</button>
);
}
在 useFormStatus
出现之前,我们处理表单提交状态的通用流程是:
- 在表单所在的父组件中,定义一个加载状态,例如
const [isPending, setIsPending] = useState(false)
。 - 当用户点击提交按钮时,我们需要手动调用
setIsPending(true)
。 - 然后,把 isPending 这个状态通过属性(prop)一层层传递给需要它的子组件,比如提交按钮
<SubmitButton disabled={isPending} />
。 - 当异步操作(比如网络请求)结束后,再手动调用
setIsPending(false)
。 这个过程不仅代码繁琐,更重要的是造成了父组件和子组件的紧密耦合。父组件必须管理所有状态,并显式地将这些状态“告诉”子组件。
useFormStatus
的作用就是为了打破这种耦合。它允许提交按钮这样的子组件,能够自己主动地感知它所在的 <form> 当前是否正在提交,而不再需要父组件通过属性来通知它。
新的 use API:条件式资源读取
React 19 引入了新的 use API,可以在渲染中读取资源如 Promise 和 Context。
读取 Promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { use, Suspense } from 'react';
// use API 会 "解包" Promise
// 如果在 pending,它会暂停渲染,并向上寻找 Suspense
// 如果 reject,它会抛出错误,并向上寻找 ErrorBoundary
function Comments({ commentsPromise }) {
// use 会暂停直到 promise 解析
const comments = use(commentsPromise);
// 组件只关心成功后的渲染逻辑
return comments.map(comment => (
<div key={comment.id}>{comment.text}</div>
));
}
function BlogPost({ commentsPromise }) {
return (
<ErrorBoundary fallback={<p>⚠️ Something went wrong while fetching the post!</p>}>
<Suspense fallback={<div>加载评论中...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
</ErrorBoundary>
);
}
条件式 Context 读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { use } from 'react';
function Heading({ children }) {
if (children == null) {
return null;
}
// 这在 useContext 中是不可能的,因为有早期返回
const theme = use(ThemeContext);
return (
<h1 style={ { color: theme.color } }>
{children}
</h1>
);
}
useContext
仍然是读取 Context 的主要方式。use 提供了一种在渲染期间(包括在条件语句和循环中)读取资源的补充能力,这使得 useContext 的使用场景更加纯粹。
Server Components:全栈架构的完善
什么是 Server Components?
Server Components 是一种新的组件类型,可以在服务器上提前渲染,与客户端应用程序或 SSR 服务器分离。
Server Components 的优势:
- 零客户端 Bundle 大小:服务器组件不会被打包到客户端
- 直接访问服务器资源:可以直接读取数据库、文件系统等
- 自动代码分割:只有需要的客户端代码才会被发送
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ServerComponent.server.js
import { db } from './database';
export default async function ServerComponent() {
// 直接在服务器上访问数据库
const posts = await db.posts.findMany();
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
</article>
))}
</div>
);
}
Server Actions
Server Actions 允许客户端组件调用在服务器上执行的异步函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// actions.js
'use server';
export async function createPost(formData) {
const title = formData.get('title');
const content = formData.get('content');
// 服务器端操作
const post = await db.posts.create({
data: { title, content }
});
return post;
}
// ClientComponent.js
import { createPost } from './actions';
export default function CreatePost() {
return (
<form action={createPost}>
<input name="title" placeholder="标题" />
<textarea name="content" placeholder="内容" />
<button type="submit">创建文章</button>
</form>
);
}
其它改进
ref 作为 prop
从 React 19 开始,你可以直接在函数组件中访问 ref 作为 prop。
1
2
3
4
5
6
7
8
9
// React 19 之前需要 forwardRef
const MyInput = forwardRef(({ placeholder }, ref) => {
return <input placeholder={placeholder} ref={ref} />;
});
// React 19 直接使用
function MyInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
Context 作为 Provider
React 19 允许直接渲染 Context 作为 Provider。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const ThemeContext = createContext('light');
// React 19 之前
function App() {
return (
<ThemeContext.Provider value="dark">
<Header />
</ThemeContext.Provider>
);
}
// React 19
function App() {
return (
<ThemeContext value="dark">
<Header />
</ThemeContext>
);
}
文档元数据支持
React 19 添加了对在组件中渲染文档元数据标签的本地支持。
1
2
3
4
5
6
7
8
9
10
11
12
13
function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<meta name="author" content={post.author} />
<link rel="canonical" href={`/posts/${post.slug}`} />
<div>{post.content}</div>
</article>
);
}
样式表支持
React 19 为样式表提供了更深度的集成,支持 precedence
属性来管理样式表的插入顺序。
1
2
3
4
5
6
7
8
9
10
11
function Component() {
return (
<Suspense fallback="加载中...">
<link rel="stylesheet" href="critical.css" precedence="high" />
<link rel="stylesheet" href="main.css" precedence="default" />
<div className="styled-content">
内容
</div>
</Suspense>
);
}
升级指南
安装 React 19
1
npm install react@19 react-dom@19
主要破坏性变更
新的 JSX Transform 是必需的
- 需要更新 Babel 或 TypeScript 配置
去除了一些过时的 API
React.Children.toArray
的行为改变- 一些 legacy 的 ref 处理方式
严格模式的增强
- 开发模式下更严格的检查
升级步骤
更新依赖
1 2
npm install react@19 react-dom@19 npm install @types/react@19 @types/react-dom@19 # TypeScript 项目
更新 Root API(如果还在使用旧版本)
1 2 3 4 5
// 确保使用 createRoot import { createRoot } from "react-dom/client"; const root = createRoot(document.getElementById("root")); root.render(<App />);
逐步采用新特性
- 开始在新组件中使用 Actions
- 将复杂的异步状态管理迁移到 useActionState
- 考虑使用 Server Components(如果适用)
性能提升
React 19 在性能方面有显著改进:
- Actions 的自动批处理减少了不必要的重新渲染
- Server Components显著减少了客户端 Bundle 大小
- 改进的 Suspense提供了更好的用户体验
- 优化的 hydration处理第三方脚本和浏览器扩展
实际应用建议
适合立即使用的特性
- Actions + useActionState:适合所有表单处理
- ref 作为 prop:简化组件设计
- 文档元数据:改善 SEO 和用户体验
需要谨慎考虑的特性
- Server Components:需要支持的框架(Next.js 15+、Remix 等)
- use API:需要重新思考数据获取模式
迁移策略
- 新项目:直接使用 React 19 和新特性
- 现有项目:
- 先升级到 React 19
- 逐步迁移表单处理到 Actions
- 考虑长期的 Server Components 采用
总结
React 19 是一个重要的里程碑版本,它不仅带来了 Actions 这样的革命性特性,还完善了全栈架构的支持
- Actions 简化了复杂的异步状态管理,让表单处理变得优雅
- Server Components 为性能优化开辟了新路径
- 现代化的 API 设计减少了样板代码
- 更好的开发体验和错误处理