React 19 useOptimistic useFormStatus 表單處理完全指南:告別 useState 的繁瑣時代
你是否也受夠了在 React 表單裡堆滿 useState、useEffect、isLoading、error 這些狀態管理的樣板程式碼?每次寫個登入表單就像在蓋房子,地基都要自己灌。React 19 終於正式推出了一系列專為表單設計的 Hook,讓我這個寫了五年 React 的前端開發者,第一次覺得表單處理可以這麼優雅。
這篇文章會帶你完整認識 React 19 的四大表單新武器:useOptimistic、useFormStatus、useActionState,以及重新定位的 use() Hook。我們不只講概念,更會用實際程式碼示範每個 Hook 怎麼用、什麼時候用,以及如何跟 Next.js App Router 全攻略 中介紹的 Server Actions 無縫整合。
useFormStatus:掌握表單提交狀態的最佳方式
先從最直覺的 useFormStatus 開始。這個 Hook 解決了一個我們長期以來用 useState 土法煉鋼的問題:如何知道表單正在提交中?
以前我們得這樣寫:
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
try {
await submitForm(data);
} finally {
setIsSubmitting(false);
}
};現在用 useFormStatus,你只需要在 <form> 的子元件裡呼叫它:
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending, data, method, action } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '送出中...' : '送出'}
</button>
);
}重點在於:useFormStatus 必須在 <form> 的子元件中使用,不能直接放在含有 <form> 的同一個元件。它回傳的 pending 布林值會自動追蹤表單的提交狀態,完全不需要手動管理。這個設計背後的哲學是「讓狀態跟著 DOM 結構走」,而不是讓開發者自己維護一堆變數。
useActionState:統一表單動作與狀態管理
useActionState(原名 useFormState,後來改名以避免混淆)是我個人認為 React 19 最強大的表單 Hook。它把表單的 action、狀態、以及提交狀態三件事整合在一起:
import { useActionState } from 'react';
function LoginForm() {
const [state, formAction, isPending] = useActionState(
async (previousState, formData) => {
const email = formData.get('email');
const password = formData.get('password');
const result = await login(email, password);
if (result.error) {
return { error: result.error };
}
return { success: true };
},
{ error: null }
);
return (
<form action={formAction}>
<input name="email" type="email" />
<input name="password" type="password" />
{state.error && <p className="error">{state.error}</p>}
<button disabled={isPending}>
{isPending ? '登入中...' : '登入'}
</button>
</form>
);
}注意看,我們完全不需要 onSubmit、preventDefault、或任何手動的狀態切換。useActionState 接收一個 async 函式和初始狀態,回傳當前狀態、綁定到 form action 的函式,以及 isPending。這個模式跟 Server Actions 搭配起來簡直是天作之合。
useOptimistic:讓使用者感覺不到延遲的秘密武器
Optimistic UI 不是新概念,但以前要實作得寫不少程式碼。useOptimistic 把這件事變得非常簡單:
import { useOptimistic } from 'react';
function TodoList({ todos, addTodoAction }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(currentTodos, newTodo) => [
...currentTodos,
{ ...newTodo, sending: true }
]
);
return (
<form action={async (formData) => {
const title = formData.get('title');
addOptimisticTodo({ title, id: crypto.randomUUID() });
await addTodoAction(title);
}}>
<input name="title" />
<button type="submit">新增</button>
<ul>
{optimisticTodos.map(todo => (
<li key={todo.id} style={{ opacity: todo.sending ? 0.6 : 1 }}>
{todo.title}
</li>
))}
</ul>
</form>
);
}使用者按下新增後,新的待辦事項會「立刻」出現在列表中(帶有半透明效果表示正在送出),等到伺服器回應後自動更新為正式資料。如果失敗,React 會自動回滾到之前的狀態。這對使用者體驗的提升是巨大的,特別是在網路較慢的環境下。
use() Hook:非同步資料的新典範
use() 是 React 19 引入的另一個革命性 Hook。它可以在 render 階段直接讀取 Promise 或 Context,搭配 Suspense 使用:
import { use, Suspense } from 'react';
function UserProfile({ userPromise }) {
const user = use(userPromise);
return <div>{user.name}</div>;
}
// 在父元件中
<Suspense fallback={<p>載入中...</p>}>
<UserProfile userPromise={fetchUser(id)} />
</Suspense>跟 useEffect + useState 的組合相比,use() 讓資料取得的邏輯更宣告式,也更容易搭配 React Server Components Streaming 效能優化 中提到的 streaming 機制,讓頁面逐步渲染。
搭配 Server Actions 的完整實戰
這些 Hook 真正的威力,要在搭配 Server Actions 時才能完全展現。以下是一個完整的評論表單範例:
// actions.ts (Server Action)
'use server';
export async function addComment(prevState, formData) {
const content = formData.get('content');
if (!content || content.length < 3) {
return { error: '評論至少需要 3 個字' };
}
await db.comments.create({ data: { content } });
revalidatePath('/posts');
return { success: true, error: null };
}
// CommentForm.tsx (Client Component)
'use client';
import { useActionState, useOptimistic } from 'react';
import { addComment } from './actions';
export function CommentForm({ comments }) {
const [optimistic, addOptimistic] = useOptimistic(
comments,
(curr, newComment) => [...curr, { ...newComment, pending: true }]
);
const [state, action, isPending] = useActionState(
async (prev, formData) => {
addOptimistic({ content: formData.get('content'), id: Date.now() });
return addComment(prev, formData);
},
{ error: null }
);
return (
<>
<form action={action}>
<textarea name="content" required />
{state.error && <p>{state.error}</p>}
<SubmitButton />
</form>
<ul>
{optimistic.map(c => (
<li key={c.id} className={c.pending ? 'opacity-50' : ''}>
{c.content}
</li>
))}
</ul>
</>
);
}這個模式結合了 useActionState 處理表單狀態與錯誤、useOptimistic 提供即時回饋,再透過 Server Action 在伺服器端驗證與儲存。整個流程不需要 API route,不需要 fetch,不需要手動管理 loading 狀態。如果你正在使用 Next.js App Router,搭配 Next.js use cache 完全指南 的快取策略,效能和體驗都能達到最佳。
從傳統寫法遷移的實用建議
如果你的專案還在用傳統的 useState + useEffect 管理表單,以下是我建議的遷移步驟:
- 先替換提交按鈕:把提交按鈕抽成獨立元件,使用
useFormStatus。這是最小幅度的改動,風險最低。 - 將 onSubmit 改為 form action:把
onSubmithandler 改成action屬性,配合useActionState。記得移除preventDefault。 - 加入 Optimistic UI:對於列表類的操作(新增、刪除),用
useOptimistic包裝現有資料。 - Progressive Enhancement:使用
action屬性的表單,在 JavaScript 未載入時仍然可以正常提交,這是一個很容易被忽略但非常重要的優點。
不需要一次全部改完。我自己的做法是新功能用新 Hook,舊程式碼等到需要修改時再逐步遷移。
常見模式與注意事項
在實際使用這些 Hook 幾個月後,我整理了幾個值得注意的地方:
- useFormStatus 的作用域:它只能感知最近的父級
<form>,如果你的按鈕不在 form 裡面,它會回傳pending: false。 - useOptimistic 的回滾時機:當 action 完成(無論成功或失敗),optimistic 狀態會自動被真實資料取代。你不需要手動處理回滾。
- useActionState 的初始狀態:第一次 render 時
isPending是false,state是你傳入的初始值。確保你的 UI 能正確處理初始狀態。 - 錯誤處理:在 action 函式中用 return 回傳錯誤狀態,而不是 throw。throw 會被 Error Boundary 攔截,return 則會更新 state。
結語
React 19 的這些表單 Hook 不只是語法糖,它們代表了 React 對表單處理的重新思考。從「開發者手動管理一切」轉變為「框架幫你處理常見模式」,這個方向是對的。特別是搭配 Server Actions 之後,前後端的邊界變得更加模糊,開發體驗也更加流暢。
如果你還沒開始用這些新 Hook,我建議從一個小表單開始嘗試。你會發現,原本需要 50 行程式碼的表單邏輯,現在可能 15 行就搞定了。這不只是省程式碼的問題,而是讓你把注意力放在真正重要的事情上——使用者體驗。
繼續閱讀
React Signals 是什麼?2026 年最值得關注的前端狀態管理新方案完整解析
相關文章
React Signals 是什麼?2026 年最值得關注的前端狀態管理新方案完整解析
深入解析 React Signals 狀態管理方案,與 Zustand、Redux 效能比較,含實作範例與遷移指南。
React 19 useActionState 完整教學:搭配 useOptimistic 與 Server Actions 打造流暢表單體驗
React 19 的 useActionState 讓表單狀態管理變得超簡單,搭配 useOptimistic 還能實現即時 UI 回饋。這篇教學帶你從基礎用法到搭配 Server Actions 的完整實戰,用更少程式碼打造流暢表單體驗。
你可能也喜歡
探索其他領域的精選好文