React19新特性
Actions
在 React 应用中,一个常见的用例是执行数据变更,然后响应更新状态。例如,当用户提交一个表单来更改他们的名字,你会发起一个 API 请求,然后处理响应。在过去,你需要手动处理待定状态、错误、乐观更新和顺序请求。
React18之前:
jsx// 没有 Actions 之前 function UpdateName({}) { const [name, setName] = useState(""); const [error, setError] = useState(null); const [isPending, setIsPending] = useState(false); const handleSubmit = async () => { setIsPending(true); const error = await updateName(name); setIsPending(false); if (error) { setError(error); return; } redirect("/path"); }; return ( <div> <input value={name} onChange={(event) => setName(event.target.value)} /> <button onClick={handleSubmit} disabled={isPending}> Update </button> {error && <p>{error}</p>} </div> ); }
React18:可以使用
useTransition
来处理待定状态jsx// 使用 Actions 中的待定状态 function UpdateName({}) { const [name, setName] = useState(""); const [error, setError] = useState(null); const [isPending, startTransition] = useTransition(); const handleSubmit = () => { startTransition(async () => { const error = await updateName(name); if (error) { setError(error); return; } redirect("/path"); }) }; return ( <div> <input value={name} onChange={(event) => setName(event.target.value)} /> <button onClick={handleSubmit} disabled={isPending}> Update </button> {error && <p>{error}</p>} </div> ); }
React19:在 Action 中自动处理待定状态、错误、表单和乐观更新
jsx// 使用表单的 Actions 和 useActionState function ChangeName({ name, setName }) { const [error, submitAction, isPending] = useActionState( async (previousState, formData) => { const error = await updateName(formData.get("name")); if (error) { return error; } redirect("/path"); return null; }, null, ); return ( <form action={submitAction}> <input type="text" name="name" /> <button type="submit" disabled={isPending}>Update</button> {error && <p>{error}</p>} </form> ); }
Actions 其实就是异步过渡函数:
在 Actions 的基础上,React 19 引入了三个不同的 hooks 来处理不同的情况:
useOptimistic
:用于管理乐观更新useActionState
:用于处理 Actions 的常见情况useFormStatus
:来支持表单中 Actions 的常见情况(react-dom)
乐观更新和非紧急更新对比:
- 乐观更新(Optimistic Update)
- 定义:假设用户操作大概率成功,在异步请求完成前直接更新 UI,若后续请求失败则回滚状态
- 场景:适用于需要即时反馈的交互,如聊天消息发送、购物车商品增减等
- API:通过
useOptimistic
钩子实现临时状态副本的更新与回滚 - 特点:
- 主动预测:UI 变化不依赖后端响应,直接体现用户操作结果
- 容错机制:失败时自动恢复原始状态,避免数据不一致
- 非紧急更新(Transition)
- 定义:通过
startTransition
或useTransition
标记的更新,允许 React 将其延迟处理,避免阻塞高优先级交互(如输入框输入) - 场景:适用于非直接交互的更新,如下拉筛选、批量数据加载等
- 特点:
- 调度优先级:属于 React 内部的任务调度机制,优化渲染性能
- 无状态回滚:仅控制更新时机,不处理状态回滚逻辑
- 定义:通过
React 19 在 react-dom
中将 Actions 集成到了新的 <form>
功能中。现在 <form>
、<input>
和 <button>
的 action
和 formAction
属性可以直接接收函数,实现表单的自动提交。
- 使用方式示例:
<form action={actionFunction}>
- 当
<form>
的 Action 成功后,React 会自动重置非受控组件的表单。 - 若需要手动重置表单,可使用新的
requestFormReset
API。
下面是三个hooks的使用示例:
useActionState
jsxconst [error, submitAction, isPending] = useActionState( async (previousState, newName) => { const error = await updateName(newName); if (error) { // 你可以返回操作的任何结果。 // 这里,我们只返回错误。 return error; } // 处理成功的情况。 return null; }, null, );
useFormStatus
import {useFormStatus} from 'react-dom';
function DesignButton() {
const {pending} = useFormStatus();
return <button type="submit" disabled={pending} />
}
useOptimistic
useOptimistic
Hook 会在updateName
请求进行时立即渲染optimisticName
。当更新完成或出错时,React 将自动切换回currentName
值。
jsxfunction ChangeName({currentName, onUpdateName}) { const [optimisticName, setOptimisticName] = useOptimistic(currentName); const submitAction = async formData => { const newName = formData.get("name"); setOptimisticName(newName); const updatedName = await updateName(newName); onUpdateName(updatedName); }; return ( <form action={submitAction}> <p>Your name is: {optimisticName}</p> <p> <label>Change Name:</label> <input type="text" name="name" disabled={currentName !== optimisticName} /> </p> </form> ); }
新的 API:use
use
可以读取 promise
。
传统的方式(不使用
use
)tsx/* eslint-disable @typescript-eslint/no-unused-expressions */ import { useState, useEffect } from 'react' import './App.css' interface DisplayDataProps { promise: Promise<string> | null } // 模拟随机成功/失败的请求 function fetchData(): Promise<string> { return new Promise((resolve, reject) => { setTimeout(() => { Math.random() > 0.5 ? resolve('数据加载成功!') : reject(new Error('❌ 网络请求失败')) }, 1000) }) } function DisplayData({ promise }: DisplayDataProps) { // ✅ 传统方式:手动管理三个状态 const [data, setData] = useState<string | null>(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState<string | null>(null); // ✅ 用 useEffect 监听 promise 变化 useEffect(() => { if (!promise) return; let isMounted = true; setIsLoading(true); setError(null); setData(null); promise .then(result => { if (isMounted) { setData(result); setIsLoading(false); } }) .catch(err => { if (isMounted) { setError(err.message); setIsLoading(false); } }); return () => { isMounted = false; }; }, [promise]); // ✅ 根据状态渲染不同内容 if (error) { return <p style={{ color: 'red' }}>{error}</p>; } if (isLoading) { return <p>⌛ 加载中...</p>; } return <p>{data}</p>; } export default function App() { const [promise, setPromise] = useState<Promise<string> | null>(null); const handleClick = () => { setPromise(fetchData()); }; return ( <> <button onClick={handleClick}>点击加载</button> <DisplayData promise={promise} /> </> ) }
使用
use
读取promise
tsx/* eslint-disable @typescript-eslint/no-unused-expressions */ import { use, Suspense, useState } from 'react' import './App.css' interface DisplayDataProps { promise: Promise<string | void> } // 模拟随机成功/失败的请求 function fetchData(): Promise<string> { return new Promise((resolve, reject) => { setTimeout(() => { Math.random() > 0.5 ? resolve('数据加载成功!') : reject('❌ 网络请求失败') }, 1000) }) } function DisplayData({ promise }: DisplayDataProps) { // ✅ use() 会自动等待 Promise 完成 const data = use(promise) as string return <p>{data}</p> } export default function App() { const [promise, setPromise] = useState<Promise<string | void> | null>(null) const [error, setError] = useState(null) const handleClick = () => { // 重置状态 setError(null) // ✅ 核心:用 .catch 捕获错误并更新状态 setPromise(fetchData().catch(err => setError(err))) } return ( <> <button onClick={handleClick}>点击加载</button> {/* 错误提示 */} {error && <p style={{ color: 'red' }}>{error}</p>} {/* ✅ 仅在无错误时显示 Suspense */} {!error && promise && ( <Suspense fallback={<p>⌛ 加载中...</p>}> <DisplayData promise={promise} /> </Suspense> )} </> ) }
使用 use
的优化方案:通过 Promise.all
并行请求,减少等待时间
function Profile({ userId }) {
const [user, posts] = use(Promise.all([fetchUser(userId), fetchPosts(userId)]));
return <Dashboard user={user} posts={posts} />;
}
运行时序对比:
传统方案流程
text初次渲染 → 显示加载 → 触发 useEffect ⬇ Promise 完成 → 更新状态 → 触发重新渲染 ⬇ 根据新状态显示数据/错误
使用
use
的流程text渲染组件 → use(promise) → 自动挂起 → Suspense 接管 ⬇ Promise 完成 → React 自动重新渲染
use
可以条件式读取 context
。
传统方式(
useContext
):必须在组件顶层调用,无法在条件分支或循环中使用jsxfunction Button({ show }) { const theme = useContext(ThemeContext); // 必须始终调用 if (!show) return null; // 即使不渲染,仍会订阅 Context }
使用
use
后:支持按需动态读取 Context,减少不必要的订阅jsxfunction Button({ show }) { if (show) { const theme = use(ThemeContext); // 仅在需要时读取 return <button style={{ theme }}>Click</button>; } return null; }
ref
的改进
可以在函数组件中将 ref
作为 prop 进行访问:新的函数组件将不再需要 forwardRef
function MyInput({placeholder, ref}) {
return <input placeholder={placeholder} ref={ref} />
}
//...
<MyInput ref={ref} />
refs 支持清理函数:当组件卸载时,React 将调用从 ref
回调返回的清理函数。
例如,你可以在 ref
改变时取消订阅事件:
<input
ref={(ref) => {
// ref 创建
// 新特性: 当元素从 DOM 中被移除时
// 返回一个清理函数来重置 ref
return () => {
// ref cleanup
};
}}
/>
useDeferredValue
初始化 value
useDeferredValue
添加了一个 initialValue
选项:
function Search({deferredValue}) {
// On initial render the value is ''.
// Then a re-render is scheduled with the deferredValue.
const value = useDeferredValue(deferredValue, '');
return (
<Results query={value} />
);
}
当提供了 initialValue 时, useDeferredValue
将在组件的初始渲染中返回它作为 value
,重新渲染时返回 deferredValue
<Context>
的使用方式
在 React 19 中,可以将 <Context>
渲染为提供者,无需再使用 <Context.Provider>
了:
const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
还有一些服务器端的更新和错误提示的修改:https://zh-hans.react.dev/blog/2024/12/05/react-19


热爱编程,开源社区活跃参与者