React 19 實戰:併發特性、Actions 與新 Hooks
React 19 為什麼值得升級
React 19 把過去幾年實驗性的併發特性正式收斂為穩定 API,並補齊了「表單 / 資料變更」這一長期短板。核心變化可以概括為一句話:用 Action 包裹「寫操作」,用併發原語包裹「讀操作」,讓 UI 在等待中不卡頓。
| 能力 | React 18 | React 19 |
|---|---|---|
| Server Components | 實驗(Next.js 等框架) | 成為文件級穩定特性 |
| 表單提交 | 手寫 useState + onSubmit |
useActionState / <form action> |
| 讀取 Promise / Context | use 不適用 |
use() 可在渲染中讀 Promise/Context |
| ref | 只能 useRef + forwardRef |
可直接作為 Props 傳遞 |
| 樂觀更新 | 手寫狀態切換 | useOptimistic |
| 併發過渡 | useTransition |
useTransition + useDeferredValue 增強 |
Actions:把「寫操作」變成一等公民
Action 是一個「可以被 React 等待」的函式。配合 <form action> 或 useActionState,React 會自動管理 pending 狀態並停用提交按鈕。
"use client";
import { useActionState } from "react";
async function subscribe(prevState: { ok: boolean; msg: string }, formData: FormData) {
const email = formData.get("email");
const res = await fetch("/api/subscribe", {
method: "POST",
body: JSON.stringify({ email }),
});
if (!res.ok) return { ok: false, msg: "訂閱失敗,請重試" };
return { ok: true, msg: "訂閱成功!" };
}
export function SubscribeForm() {
const [state, formAction, pending] = useActionState(subscribe, {
ok: false,
msg: "",
});
return (
<form action={formAction}>
<input name="email" type="email" required />
<button disabled={pending}>{pending ? "提交中…" : "訂閱"}</button>
<p>{state.msg}</p>
</form>
);
}
useFormStatus 則讓它能在子元件裡拿到父表單的狀態,無需層層透傳:
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? "儲存中…" : "儲存"}</button>;
}
設定類資料(如介面回傳的表單預設值)可先用 JSON 格式化 工具整理,再回填到
initialState,避免手寫大物件出錯。
use:在渲染中讀 Promise 與 Context
use() 是 React 19 引入的「渲染期讀取」原語:它能直接 await 一個 Promise,也能讀 Context,並會在 Suspense 邊界處掛起。
import { use } from "react";
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise); // 掛起直到 resolve
return (
<ul>
{comments.map((c) => (
<li key={c.id}>{c.body}</li>
))}
</ul>
);
}
注意:use() 必須在元件或 Hook 的渲染階段呼叫,不能放在 try/catch 之外的條件分支裡——它不是一個普通函式,而是告訴 React「這裡可能要等」。
併發渲染:讓等待不卡 UI
useTransition:把「慢更新」標記為非緊急
import { useTransition, useState } from "react";
function Search() {
const [query, setQuery] = useState("");
const [list, setList] = useState<Item[]>([]);
const [isPending, startTransition] = useTransition();
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
const value = e.target.value;
setQuery(value); // 輸入框立刻回應
startTransition(() => {
setList(filterExpensive(value)); // 重計算包在 transition 裡
});
}
return (
<div>
<input value={query} onChange={onChange} />
<ul style={{ opacity: isPending ? 0.6 : 1 }}>
{list.map((i) => (
<li key={i.id}>{i.name}</li>
))}
</ul>
</div>
);
}
useDeferredValue:自動「延遲」一個值
當輸入與渲染解耦時,useDeferredValue 更省心:
const deferred = useDeferredValue(query);
const results = useMemo(() => filterExpensive(deferred), [deferred]);
兩者的差別:useTransition 包裹「一段狀態更新」,useDeferredValue 派生「一個被延遲的值」,通常和 useMemo 配合。
useOptimistic:樂觀更新開箱即用
import { useOptimistic, useRef } from "react";
type Msg = { id: number; text: string; sending?: boolean };
function Chat() {
const [messages, setMessages] = useOptimistic<Msg[]>([]);
const input = useRef<HTMLInputElement>(null);
async function send(formData: FormData) {
const text = formData.get("text") as string;
setMessages((list) => [...list, { id: Date.now(), text, sending: true }]);
await fetch("/api/chat", { method: "POST", body: JSON.stringify({ text }) });
}
return (
<form action={send}>
<input ref={input} name="text" />
{messages.map((m) => (
<p key={m.id}>{m.text}{m.sending ? "(發送中)" : ""}</p>
))}
</form>
);
}
ref 作為 Props 與文件元資料
ref 不再是特例
function MyInput({ ref }: { ref: React.Ref<HTMLInputElement> }) {
return <input ref={ref} />;
}
// 使用:<MyInput ref={myRef} /> —— 不再需要 forwardRef
文件元資料原生支援
import { Title, Meta } from "react-dom";
function Page() {
return (
<>
<Title>頁面標題</Title>
<Meta name="description" content="頁面描述" />
</>
);
}
從 React 18 平滑遷移
- 升級依賴:
npm i react@19 react-dom@19,並升級@types/react。 - 替換
forwardRef:直接把ref寫進 Props 即可,舊寫法在 19 中已棄用但暫相容。 - 落地 Actions:把「提交後 setState + loading」的表單遷移到
useActionState。 - 用
use收口非同步:Server Components 下把資料 Promise 傳給子元件,用use()讀取。 - 接入併發:列表/搜尋類互動用
useDeferredValue或useTransition提升回應感。
常見問題 FAQ
Q1:Actions 只能在 Server Component 裡用嗎?
不是。用戶端也能用 useActionState 包裹普通非同步函式;Server Action 只是其中一種「函式直接跑在服務端」的形式。
Q2:use() 和 await 有什麼區別?
await 是 JS 執行期語意,use() 是 React 的渲染語意:它會在 Suspense 邊界掛起,並能被併發排程打斷重渲染。
Q3:useTransition 和 useDeferredValue 選哪個?
要「延遲一整段更新」用 useTransition;要「基於某個快速變化的值派生慢結果」用 useDeferredValue + useMemo。
Q4:React 19 還支援 IE 嗎?
不支援,且已徹底移除舊版遺留行為。現代瀏覽器環境是前提。
Q5:樂觀更新失敗怎麼回滾?
useOptimistic 會在傳入的 Action 拋錯時自動回滾到真實狀態;你只需保證 setMessages 的「真實資料來源」在失敗時不變即可。
工具推薦
在 React 19 開發中,以下 工具庫 能幫上忙:
- JSON 格式化 — 整理介面回傳值與表單初始狀態
- 顏色轉換 — HEX/RGB/HSL 互轉,配合主題
- 顏色對比度 — 校驗按鈕/文字 WCAG 可存取性
- HTTP 狀態碼 — 排查 Action 介面回傳碼
React 19 的精髓不是「又多了幾個 Hook」,而是把「寫操作(Action)」與「讀操作(併發/use)」用統一心智模型管起來:UI 在等待資料時不卡頓,在提交變更時狀態自管。掌握 Action + 併發原語,你就握住了現代 React 的鑰匙。
本站提供瀏覽器本地工具,免註冊即可試用 →