React 19 补上了以往应用代码要自己处理的缺口:异步 form Actions、optimistic updates、用 use 读取 promises,以及 refs、context 与 document metadata 周围更少的繁琐写法。
React 19.2 加入 Activity、useEffectEvent,以及更好的性能工具。把升级与功能采用当成分开的步骤。
React Compiler 是一项分开的、stable、optional build-time optimizer。它从 HIR 到 cache 的 pipeline 与当前 adoption guidance,见 React Compiler Internals。
1. Actions
Action 是在 Transition 里执行的函数。它可以做异步工作,同时由 React 管理周围的 pending state、errors,以及最终的 state update。
function UpdateName() {
async function updateName(formData: FormData) {
await saveName(String(formData.get("name") ?? ""))
}
return (
<form action={updateName}>
<input name="name" />
<SubmitButton />
</form>
)
}- 最显眼的用途是
<form>上的actionprop。把函数传进去,而不是拦截onSubmit、读取表单、追踪 loading flag,再手动重置。 - 当 Action 成功时,React 会自动重置 uncontrolled fields。同一个函数也可以指定给 button 的
formAction,让不同 submit buttons 有不同行为。 - Actions 不只限于 forms,但表单整合是它们砍掉最多例行 state management 的地方。
2. useActionState 与 useFormStatus
useActionState 把 Action 接到由其前一次结果产生的 state。它返回 current state、一个包装过的 Action,以及 isPending 值。
type FormState = { message: string; ok: boolean }
async function saveEmail(
previousState: FormState,
formData: FormData
): Promise<FormState> {
const email = String(formData.get("email") ?? "")
if (!email.includes("@")) return { message: "Enter a valid email.", ok: false }
await subscribe(email)
return { message: "You are subscribed.", ok: true }
}
function SubmitButton() {
const { pending } = useFormStatus()
return <button type="submit" disabled={pending}>{pending ? "Subscribing..." : "Subscribe"}</button>
}
function NewsletterForm() {
const [state, formAction] = useActionState(saveEmail, { message: "", ok: false })
return (
<form action={formAction}>
<input name="email" type="email" />
<SubmitButton />
<p aria-live="polite">{state.message}</p>
</form>
)
}- Action 会在平常的参数之前先收到 previous state。对 form Action 来说,第二个参数就是提交的
FormData。 useActionState适合 server result 应该变成 UI state 的时候。useFormStatus适合只需要知道周围 form 是否 pending 的 controls。- Failure: 在创建 form 的同一个 component 里调用
useFormStatus。它读的是 parent form,所以必须从渲染在该 form 里面 的 child 调用。
3. useOptimistic
useOptimistic 让 UI 在 Action 跑的时候立刻显示 预期结果,完成后再与真实 state 对账。
type Message = { id: string; text: string; sending?: boolean }
function MessageThread({ initialMessages }: { initialMessages: Message[] }) {
const [messages, setMessages] = useState(initialMessages)
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(current, text: string) => [...current, { id: crypto.randomUUID(), text, sending: true }]
)
async function sendMessage(formData: FormData) {
const text = String(formData.get("message") ?? "")
addOptimisticMessage(text)
const saved = await saveMessage(text)
setMessages((current) => [...current, saved])
}
return (
<form action={sendMessage}>
{optimisticMessages.map((m) => (
<p key={m.id}>{m.text}{m.sending ? " (sending...)" : ""}</p>
))}
<input name="message" />
</form>
)
}- Optimistic value 是 暂时的。Action 结束后,React 会回到真实 state 或 props 提供的值。
- Source of truth 仍然要在 optimistic reducer 外面更新。
- Failure: 把 optimistic UI 当成已提交的成功。Destructive operations,以及假装成功可能误导用户的任何事,都该更谨慎。
4. 用 use 读取 Resources
use 在 rendering 期间读取 resource——今天是 Promise 或 Context。Pending Promise 会 suspend 到最近的 <Suspense> fallback。Rejection 会走到最近的 Error Boundary。
function ProfileCard({ profilePromise }: { profilePromise: Promise<Profile> }) {
const profile = use(profilePromise)
return <h2>{profile.name}</h2>
}
function ProfilePage({ profilePromise }: { profilePromise: Promise<Profile> }) {
return (
<Suspense fallback={<p>Loading profile...</p>}>
<ProfileCard profilePromise={profilePromise} />
</Suspense>
)
}- 与 Hooks 不同,
use可以放在 conditions 与 loops 里。它仍然必须在 React 正在 render 一个 component 或 custom Hook 时跑。 use也可以有条件地读取 Context——在 muted heading 上跳过use(ThemeContext),在 active 的那个上调用。use是通往可能尚未就绪的 resource 的桥,不是每一个 data-fetching library 的替代品。- Failure: 在每次 Client Component render 时创建新的 Promise。那会反复 suspend。Promise 应该来自 framework、cache,或 Server Component,好让同一个 resource 能被复用。
5. Refs 与 Context 少了繁琐写法
Function components 在 React 19 里可以把 ref 当成普通 prop 接收。新 components 不再需要只为了暴露一个 element 而写 forwardRef。
function SearchInput(props: ComponentPropsWithRef<"input">) {
return <input type="search" {...props} />
}
<canvas
ref={(node) => {
if (!node) return
const observer = new ResizeObserver(() => draw(node))
observer.observe(node)
return () => observer.disconnect()
}}
/>- 既有的
forwardRefcomponents 仍然能用。React 计划在迁移有时间发生之后再 deprecateforwardRef。 - Ref callbacks 可以返回 cleanup functions——上面的
ResizeObserverdisconnect。 - Context object 可以直接渲染:
<ThemeContext value="dark">,而不是<ThemeContext.Provider value="dark">。 - Failure: 把更严格的 ref-callback TypeScript 当成意外。Callbacks 现在可以返回 cleanup,所以类型会拒绝以前被忽略的意外 returns。
6. Document Metadata 与 Resources
React 19 理解渲染在 components 里的 <title>、<meta> 与 <link> tags。React 会把它们移到 document 的 <head>,所以 metadata 可以住在拥有它的 route 或内容旁边。
function ProductPage({ product }: { product: Product }) {
return (
<>
<title>{product.name} – Acme Store</title>
<meta name="description" content={product.summary} />
<h1>{product.name}</h1>
</>
)
}<title>、<meta>与<link rel="canonical">可以住在拥有它们的 route 旁边。React 会把它们 hoist 进<head>。- Stylesheets 可以声明
precedence。React 会等相关 CSS,再揭示 Suspense 内容。即使多个 components 渲染同一份 script,async scripts 也会被 deduplicated。 - React DOM 暴露
preconnect、preload、preinit、preloadModule与preinitModule,给在需要它们的 element 之前就已知的 resources。 - Frameworks 已经处理了其中大部分。在直接调用这些 APIs 之前,先看 framework 的 conventions。
7. 更好的 Custom Elements 与 Hydration Errors
React 19 对 custom elements 有完整支持。Server rendering 时,strings 与 numbers 这类 primitive props 会变成 attributes。在 browser 里,当 custom element 暴露匹配的 properties 时,React 会把值赋成 properties。
function Checkout() {
return (
<payment-card
customer-id="customer_123"
options={{ appearance: "compact" }}
/>
)
}- Web components 更容易消费,不必为每个非 string 值写 React-specific wrappers。
- Hydration diagnostics 被收拢:一个 error,附带 diff,显示 server-rendered HTML 与 client output 差在哪里。
- Failure: 把更好的 message 当成修复。Mismatches 仍然来自 clocks、random values、browser-only branches、变化中的外部数据,或无效的 HTML nesting。
8. React 19.2 的 Activity
Activity 让 React 保持一段 interface mounted,同时控制它是否可见,以及它的 updates 该以多紧急的程度被处理。
function Workspace({ activeTab }: { activeTab: "editor" | "preview" }) {
return (
<>
<Activity mode={activeTab === "editor" ? "visible" : "hidden"}>
<Editor />
</Activity>
<Activity mode={activeTab === "preview" ? "visible" : "hidden"}>
<Preview />
</Activity>
</>
)
}visible显示 children、mount 它们的 effects,并正常处理 updates。hidden隐藏 children、unmount 它们的 effects,并把它们的 updates 延后到 React 没有可见工作剩下。- 与 conditional rendering 真正有用的差别是 preserved state。Hidden editor 可以保住 draft、selection 与 component state,同时 React 可以在背景准备很可能出现的下一个画面。
- Failure: 把这当成 CSS
display: none。Hidden Activities 会清理 effects,所以 subscriptions 必须能正确停止与重启。
9. React 19.2 的 useEffectEvent
Effects 常常混进两个想法:该对 dependency 做出反应的 synchronization,以及该读到最新 props、却不该重启那段 synchronization 的 event-like logic。useEffectEvent 把 event-like 那部分拆出来。
function ChatRoom({ roomId, theme }: { roomId: string; theme: "light" | "dark" }) {
const onConnected = useEffectEvent(() => {
showNotification("Connected", theme)
})
useEffect(() => {
const connection = createConnection(roomId)
connection.on("connected", () => onConnected())
connection.connect()
return () => connection.disconnect()
}, [roomId])
}- 改
theme不再会重连 chat room,但 connection event 触发时,onConnected仍会读到最新的 theme。 - Effect Events 只能从同一个 component 或 custom Hook 里的 Effects 调用。它们不应出现在 dependency arrays 里。
- 使用这个 API 需要较新的
eslint-plugin-react-hooks;linter 理解并强制这些限制。 - Failure: 用 Effect Events 掩盖缺失的 dependencies。它们应代表概念上由 Effect 触发的 logic。
10. React 19.2 的性能与 Server 改进
React 19.2 把 React Performance Tracks 加进 Chrome DevTools performance profiles。Scheduler tracks 显示 update priorities,以及 work 何时被 scheduled、blocked、rendered 或 painted。Components tracks 显示 components 何时 render,以及它们的 effects 何时 mount。
- 这是比 React DevTools Profiler 更低阶的视图。适合缓慢交互牵涉 scheduling、browser 工作与 React rendering,而不是某一个明显昂贵的 component 的时候。
cacheSignal给 React Server Component 工作一个绑在cache()entry 生命周期上的AbortSignal,好取消不再使用的 requests。- Partial pre-rendering APIs 让 framework 预先 render 一份 static shell、存下 postponed work,稍后再 resume 剩下的 server render。
- Streaming SSR 会把邻近的 Suspense boundary reveals batch 起来。Node.js 得到 Web Streams 支持;React team 仍建议在 Node 里用 Node Streams APIs,因为它们更快,也更自然地配合 compression。
- 大多数应用开发者会通过 framework 碰到
cacheSignal与 partial pre-rendering。
11. 升级到 React 19
一起升级 React、React DOM,以及它们的 TypeScript definitions,使用 framework 支持的最新 patched React 19 release。
npm install react@^19 react-dom@^19
npm install --save-dev @types/react@^19 @types/react-dom@^19- 官方 upgrade guide 包含常见 migrations 的 codemods。
ReactDOM.render/ReactDOM.hydrate→createRoot/hydrateRoot。unmountComponentAtNode→root.unmount()。findDOMNode、string refs 与this.refs被移除。Legacy Context APIs 与 function componentpropTypes被移除。- Ref callback TypeScript 规则更严格,因为 callbacks 现在可以返回 cleanup functions。
- Failure: 把升级跟一次大型 rewrite 混在一起。先让既有应用在 React 19 上能跑,再在 Actions、
use或Activity真正简化某个 pattern 的地方引入它们。
要点
Actions 协调 async mutations 与 pending 及 optimistic UI。use 协调 rendering 与 resources。Document APIs 协调 metadata、styles 与 scripts。Activity 协调可见与背景工作。useEffectEvent 把 synchronization 与 event-like behavior 分开。
最常保留的功能是 form Actions、useActionState、useOptimistic、ref as a prop,以及 useEffectEvent。use、Activity 与 server rendering APIs 更依赖 framework 支持与架构。
官方说明见 React 19 release notes、React 19.2 release notes,以及 React 19 upgrade guide。