一、Actions:异步数据操作的革命
解决了什么问题?
在过去,处理表单提交、数据变更等异步操作时,我们需要手动管理大量的状态:isLoading
, isSubmitting
, error
, data
。这导致了冗长的模板代码,并且很容易忘记处理某些状态(如竞态条件)。
React 19 的解决方案:
Actions 引入了一组全新的 API,将数据提交、状态管理和乐观更新一体化,让复杂的异步逻辑变得异常简单。
1. useActionState
这个 Hook 专门用于处理具有状态的动作(如表单提交)。它自动为你处理 Pending 状态和错误状态。
import { useActionState } from 'react';
// 一个模拟的异步API
async function updateUserName(formData) {
const name = formData.get('username');
await new Promise(resolve => setTimeout(resolve, 1000)); // 模拟延迟
if (name === 'error') {
throw new Error('Name cannot be "error"');
}
return name;
}
function UserProfile({ currentName }) {
// useActionState 参数:
// 1. 一个异步函数,接收上一个状态和表单数据
// 2. 初始状态 (这里为 null)
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
try {
const newName = await updateUserName(formData);
console.log(`Name updated to: ${newName}`);
return null; // 成功则清空错误
} catch (err) {
return err.message; // 失败则返回错误信息
}
},
null
);
return (
<form action={submitAction}>
<input
type="text"
name="username"
defaultValue={currentName}
disabled={isPending}
/>
<button type="submit" disabled={isPending}>
{isPending ? 'Updating...' : 'Update'}
</button>
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
</form>
);
}
// 在应用中使用
function App() {
return <UserProfile currentName="Alice" />;
}
代码解释:
-
useActionState
返回一个元组:[state, action, isPending]
-
我们将异步函数
submitAction
传递给<form action>
。 -
React 会自动调用该函数,并管理
isPending
状态和错误state
。
2. useOptimistic
用于在异步操作完成前,乐观地更新 UI,提供即时反馈。
import { useOptimistic, useActionState } from 'react';
function MessageList({ messages, sendMessage }) {
// useOptimistic 参数:
// 1. 原始状态 (messages)
// 2. 一个更新函数,接收当前状态和乐观更新值
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [
...state,
{ text: newMessage, id: Math.random(), sending: true }
]
);
const [error, formAction, isPending] = useActionState(
async (prevState, formData) => {
const message = formData.get('message');
addOptimisticMessage(message); // 立即乐观更新UI
await sendMessage(message); // 执行真正的异步操作
return null;
},
null
);
return (
<div>
{/* 显示乐观更新后的列表 */}
{optimisticMessages.map(msg => (
<div key={msg.id} style={{ opacity: msg.sending ? 0.5 : 1 }}>
{msg.text}
{msg.sending && ' (Sending...)'}
</div>
))}
<form action={formAction}>
<input type="text" name="message" disabled={isPending} />
<button type="submit" disabled={isPending}>Send</button>
</form>
{error && <p>{error}</p>}
</div>
);
}
3. useFormStatus
这个 Hook 必须在 <form>
的子组件中调用,它提供了当前表单的提交状态。
import { useFormStatus } from 'react-dom';
function SubmitButton() {
// useFormStatus 返回当前父级表单的状态
const { pending, data, method, action } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}
// 在表单中使用
function MyForm() {
return (
<form action={someAction}>
<input name="name" />
<SubmitButton /> {/* 这个按钮能感知到表单状态 */}
</form>
);
}
二、资源管理与元数据
1. 原生 Document Metadata 支持
解决了什么问题?
以往需要借助 react-helmet
等第三方库来动态修改 <head>
中的标签,不利于 SEO 和 SSR。
React 19 的解决方案:
现在可以直接在组件中编写 <title>
, <meta>
, <link>
等标签,React 会自动将它们提升(hoist)到文档的 <head>
中。
function BlogPost({ post }) {
return (
<article>
{/* 这些标签会被自动移动到 <head> 中 */}
<title>{post.title} - My Blog</title>
<meta name="description" content={post.excerpt} />
<meta name="author" content={post.author} />
<link rel="canonical" href={post.canonicalUrl} />
{/* 页面内容 */}
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
注意: 在具有多个根节点(如使用 <StrictMode>
)的应用中,React 会智能地合并重复的标签。
2. 样式与脚本资源管理
React 19 可以更好地管理外部资源的加载,避免重复和竞争。
import { preload, preinit } from 'react-dom';
function HomePage() {
// 预加载关键资源,优先级高
preload('https://example.com/critical-font.woff2', { as: 'font', crossorigin: 'anonymous' });
// 预初始化一个脚本,准备执行
preinit('https://example.com/analytics.js', { as: 'script' });
return (
<div>
<h1>Welcome</h1>
{/* 组件内声明的样式,React 会管理其加载顺序 */}
<link rel="stylesheet" href="https://example.com/styles.css" precedence="default" />
<script src="https://example.com/script.js" async />
</div>
);
}
-
preload
: 告诉浏览器尽快获取资源,但不确定是否使用。 -
preinit
: 比preload
更近一步,获取后并准备执行/解析。 -
precedence
: 控制样式表的加载顺序("high"
,"medium"
,"low"
),确保样式正确覆盖。
三、Server Components 稳定化
解决了什么问题?
客户端渲染(CSR)可能导致首屏加载慢、SEO 不友好。传统的服务端渲染(SSR)配置复杂。
React 19 的解决方案:
Server Components 允许在服务器上直接渲染组件,将静态内容发送给客户端,极大减少了客户端的 JS 包体积。
// 这是一个 Server Component (通常位于 app/ 目录下,如 Next.js App Router)
// 它可以直接访问后端资源,如数据库、API
async function ProductPage({ productId }) {
// 在服务器上直接获取数据,不会包含在客户端bundle中
const product = await db.products.get(productId);
const reviews = await fetch(`https://api.example.com/products/${productId}/reviews`).then(res => res.json());
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<Price price={product.price} />
{/* Reviews 可以是一个 Client Component */}
<Reviews reviews={reviews} />
</div>
);
}
// Price 组件由于没有交互性,可以留在服务器端
function Price({ price }) {
return <p>Price: ${price}</p>;
}
四、全新的 use
Hook
解决了什么问题?
传统的 Hook 规则非常严格:不能在条件语句或循环中调用。这限制了我们在渲染过程中有条件地读取资源的能力。
React 19 的解决方案:
use
是一个新的 条件性 Hook,它可以在渲染过程中读取资源(如 Context、Promise)。
import { use, Suspense } from 'react';
// 1. 使用 use 读取 Context
function ThemeButton() {
// use(Context) 可以放在条件语句中!
const theme = use(ThemeContext);
return <button style={{ color: theme.color }}>Themed Button</button>;
}
// 2. 使用 use 读取 Promise,与 Suspense 集成
function Comments({ commentsPromise }) {
// use 会"消费"这个 Promise。
// 如果 Promise 处于 pending,最近的 <Suspense> 会显示 fallback。
// 如果 Promise 被 resolve,use 返回结果。
// 如果 Promise 被 reject,最近的错误边界会捕获它。
const comments = use(commentsPromise);
return (
<div>
{comments.map(comment => (
<p key={comment.id}>{comment.text}</p>
))}
</div>
);
}
// 在父组件中使用
function BlogPost() {
const commentsPromise = fetch('/api/comments').then(res => res.json()); // 获取Promise
return (
<article>
<h1>My Post</h1>
<Suspense fallback={<p>Loading comments...</p>}>
{/* 将 Promise 作为 prop 传递给子组件 */}
<Comments commentsPromise={commentsPromise} />
</Suspense>
</article>
);
}
重要提示: use(Promise)
与 Suspense
紧密集成,是处理异步UI的新范式。
五、API 简化与质量改进
1. Ref 清理函数
解决了什么问题?
之前,当 ref
指向的 DOM 节点被卸载时,没有官方的清理机制来移除事件监听器或取消订阅。
React 19 的解决方案:
ref
回调现在可以返回一个清理函数,当节点被卸载时自动调用。
function MyComponent() {
return (
<div>
<input
ref={(node) => {
if (!node) return; // 如果 node 为 null,则不执行
// 节点挂载时:添加事件监听器
node.addEventListener('focus', handleFocus);
// 返回一个清理函数,节点卸载时自动调用
return () => {
node.removeEventListener('focus', handleFocus);
};
}}
/>
</div>
);
}
2. Context Provider 简写
提供了一种更简洁的方式来创建 Context Provider。
// Before React 19:
const ThemeContext = createContext();
function App() {
return (
<ThemeContext.Provider value="dark">
<Content />
</ThemeContext.Provider>
);
}
// With React 19:
const ThemeContext = createContext();
function App() {
// 直接使用 <ContextName> 代替 <ContextName.Provider>
return (
<ThemeContext value="dark">
<Content />
</ThemeContext>
);
}
3. 更清晰的错误报告
React 19 对错误和警告进行了分组和去重,提供了更清晰的堆栈跟踪,尤其是在 SSR hydration 不匹配时,能更准确地指出问题所在。
总结:为什么要升级到 React 19?
特性 | 带来的好处 | 适用场景 |
---|---|---|
Actions | 极大简化异步逻辑代码,内置最佳实践 | 表单提交、任何数据变更操作 |
useOptimistic | 提供即时UI反馈,提升用户体验 | 聊天应用、点赞、关注等交互 |
Document Metadata | 原生SEO支持,无需第三方库 | 任何需要动态修改页面元信息的网站 |
Server Components | 减少客户端JS体积,加速首屏加载 | 内容型网站、Dashboard、任何重视性能的应用 |
use Hook |
突破Hook规则限制,更灵活地消费资源 | 有条件地读取Context或异步数据 |
Ref 清理 | 更好的资源管理,避免内存泄漏 | 管理DOM事件监听器、第三方库初始化 |