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。