ReactReact 19useOptimisticuseActionStateServer Actions

React 19.2 完整解析:這次更新如何簡化你的開發流程

React 19.2 以「簡化」為核心:自動記住的 useOptimistic、更直觉的 useActionState、Server Actions 改進。了解這些變化如何減少樣板程式碼。

· 7 分鐘閱讀

React 19.2 沒有讓你的應用跑得更快——它讓你寫的程式碼更少。這篇文章從「簡化」的角度,完整解析 React 19.2 的三個核心改動,以及它們對日常開發的實際影響。


前言:React 的重心轉移了

過去幾年,React 的更新重點幾乎都是效能:Virtual DOM 優化、Concurrent Mode、Fiber 架構、Server Components。這些改動很了不起,但大多數開發者感受到的,其實是「學習曲線變陡了」。

React 19.2 不一樣。它的核心命題是減少樣板程式碼、降低認知負擔。你不需要懂 Concurrent Mode 也能用好這些功能。

這個轉變,其實是 React 團隊對「正確答案」的重新定義。效能優化可以靠框架自動處理,但開發者的心智負擔,終究需要人為介入才能減少。


1. useOptimistic:自動記住上次的状态

useOptimistic 並不是 React 19.2 新出的 hook——它在 React 19.0 就已經存在。但 19.2 帶來了一個關鍵的改進:自動記住(auto-remember)

舊的麻煩:手動管理暫存狀態

在 React 19.0 的 useOptimistic 實現裡,你需要自己維護一個「樂觀更新」的狀態池。當伺服器回應錯誤時,你得手動把狀態回復到上一個版本:

// React 19.0 的麻煩之處
import { useOptimistic, useState } from 'react';
import { updateUsername } from './actions';

function UsernameForm() {
  const [username, setUsername] = useState('');
  const [optimisticUsername, setOptimisticUsername] = useOptimistic(username);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    
    // 手動設定 optimistic 狀態
    const tempUsername = username;
    setOptimisticUsername(username); // 立即顯示
    
    try {
      const newUsername = await updateUsername(username);
      setUsername(newUsername);
    } catch (error) {
      // 錯誤時:需要自己回復,還要告訴使用者失敗了
      setOptimisticUsername(tempUsername);
      alert('更新失敗,請重試');
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={optimisticUsername} // 樂觀更新:立馬看到結果
        onChange={(e) => setUsername(e.target.value)}
      />
      <button type="submit">更新</button>
    </form>
  );
}

問題在於:你得自己處理「回復」邏輯。當多個並發的 optimistic 更新同時發生時,手動管理會快速失控。

React 19.2 的 auto-remember:讓框架處理這些瑣事

React 19.2 的 useOptimistic 現在可以自動記住上次的成功狀態,並在錯誤發生時自動回復:

// React 19.2:更簡潔的寫法
import { useOptimistic } from 'react';
import { updateUsername } from './actions';

function UsernameForm() {
  const [username, setUsername] = useState('');

  // React 19.2:auto-remember 讓你不需要自己維護 optimistic state
  const [optimisticUsername, setOptimisticUsername] = useOptimistic(
    username,                          // 當前的真實狀態
    (currentState, newValue) => newValue  // 樂觀更新時如何轉換
  );

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();

    // 直接更新,React 會自動處理 optimistic state
    // 不需要 try/catch、不需要自己回復狀態
    setOptimisticUsername(username);
    
    // 這個 action 現在會自動回復(如果失敗)
    const result = await updateUsername(username);
    if (result.ok) {
      setUsername(result.newUsername);
    }
    // 失敗了?React 自動幫你回復,UI 狀態不用自己管
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={optimisticUsername} // 樂觀更新
        onChange={(e) => setUsername(e.target.value)}
      />
      <button type="submit">更新</button>
    </form>
  );
}

這個改動的價值:當你同時擁有多個並發的 optimistic 更新時,auto-remember 讓 React 自己處理「哪個版本是正確的」這個問題。你只需要專心處理成功和失敗的商業邏輯,狀態管理的細節交給框架。

實際場景:電子商城的庫存扣減

電子商城中,使用者點擊「加入購物車」後,我們希望立即看見商品被加入,但實際的庫存扣減是在後端處理。如果扣減失敗(例如庫存不足),使用者應該看到商品被移除。

React 19.0 需要你自己寫這段邏輯:

// React 19.0:失敗邏輯自己處理
function AddToCartButton({ productId, stock }: { productId: string; stock: number }) {
  const [cartCount, setCartCount] = useState(0);
  const [optimisticCount, setOptimisticCount] = useOptimistic(cartCount);

  async function handleAddToCart() {
    const previousCount = cartCount;
    setOptimisticCount(cartCount + 1); // 立即加一

    const result = await addToCart(productId);

    if (!result.ok) {
      // 手動回復
      setOptimisticCount(previousCount);
      toast.error(result.error);
    } else {
      setCartCount(result.cartCount);
    }
  }

  return (
    <button onClick={handleAddToCart}>
      加入購物車({optimisticCount}/{stock})
    </button>
  );
}

React 19.2 簡化為:

// React 19.2:auto-remember 處理回復邏輯
function AddToCartButton({ productId, stock }: { productId: string; stock: number }) {
  const [cartCount, setCartCount] = useState(0);

  const [optimisticCount, updateOptimisticCount] = useOptimistic(
    cartCount,
    (currentCount) => currentCount + 1
  );

  async function handleAddToCart() {
    updateOptimisticCount(); // React 自動暫存,自動回復
    const result = await addToCart(productId);
    if (result.ok) {
      setCartCount(result.cartCount);
    }
    // 失敗了?React 自動回復,不需要自己寫回復邏輯
  }

  return (
    <button onClick={handleAddToCart}>
      加入購物車({optimisticCount}/{stock})
    </button>
  );
}

2. useActionState:表單處理的進一步簡化

useActionState(前身是 React 19.0 的 useFormState)是 React 對表單處理的官方答案。React 19.2 在這個 hook 上做了兩個重要的簡化。

改進一:跨元件共享 action 狀態

過去,useActionState 的狀態是綁定在單一元件實例上的。如果你想在多個地方共享同一個 action 的狀態(例如在表單錯誤時,旁邊的 Summary 元件也要顯示錯誤訊息),就得透過 Context 或其他方式自己架構。

React 19.2 讓 useActionState 的狀態可以跨元件共享:

// React 19.2:action 狀態可以跨元件共享
import { useActionState } from 'react';
import { submitOrder } from './actions';

// 定義 action
const submitOrderAction = async (previousState: OrderState, formData: FormData) => {
  const validated = validateOrder(formData);
  if (!validated.ok) {
    return { error: validated.message, success: false };
  }
  const result = await submitOrder(validated.data);
  return { error: null, success: true, orderId: result.orderId };
};

// 表單元件:使用 action
function OrderForm() {
  const [state, formAction, isPending] = useActionState(
    submitOrderAction,
    { success: false, error: null, orderId: null }
  );

  return (
    <form action={formAction}>
      <input name="email" type="email" required />
      <input name="productId" type="hidden" value="ITEM_001" />
      {state.error && <p className="error">{state.error}</p>}
      <button type="submit" disabled={isPending}>
        {isPending ? '處理中...' : '確認訂單'}
      </button>
    </form>
  );
}

// 另一個元件:同一個 action 狀態,不需要自己傳遞
function OrderConfirmation() {
  const [, , isPending] = useActionState(submitOrderAction, null);
  // ^ React 19.2 允許傳 null 作為初始狀態(當 action 只用於共享狀態時)
  // 這個元件只看 isPending,不需要其他狀態
  
  return isPending ? <LoadingSpinner /> : null;
}

改進二:內建錯誤邊界(Error Boundary)整合

React 19.2 的 useActionState 現在自動處理 Action 的錯誤,不需要自己包 try/catch:

import { useActionState } from 'react';
import { submitFeedback } from './actions';

function FeedbackForm() {
  const [state, formAction] = useActionState(submitFeedback, {
    submitted: false,
    error: null,
  });

  // React 19.2:錯誤自動寫入 state,不需要自己 catch
  return (
    <form action={formAction}>
      <textarea name="feedback" required />
      {state.error && <p role="alert">{state.error}</p>}
      {state.submitted && <p>感謝回饋!</p>}
      <button type="submit">送出</button>
    </form>
  );
}

關於 Server Actions 的基礎概念,請參考 React Server Actions 完整指南。本文專注於 React 19.2 的進階應用差異。


3. Server Actions 的改進:更少的 boilerplate

Server Actions 在 React 19.0 首次引入時,需要透過 use server 指令明確定義。React 19.2 簡化了這個流程,特別是在**漸進式遷移(progressive enhancement)**場景。

直接在 Client Component 使用 Server Function

React 19.0 要求 Server Actions 必須定義在 Server Component 檔案(或頂層 Module)中:

// React 19.0:需要在 server 檔案中明確標記
// actions.ts(server file)
'use server';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  await db.post.create({ data: { title } });
  revalidatePath('/posts');
}

React 19.2 允許你在 Client Component 內直接定義 async function,並透過 useActionState 使用:

// React 19.2:更簡潔的寫法
'use client';

import { useActionState } from 'react';

function CreatePostForm() {
  const [state, formAction] = useActionState(
    // 直接在 Client Component 內定義 Server Action
    async (prevState: unknown, formData: FormData) => {
      'use server'; // React 19.2 允許在 function 內使用這個指令
      const title = formData.get('title') as string;
      await db.post.create({ data: { title } });
      revalidatePath('/posts');
      return { ok: true };
    },
    { ok: false }
  );

  return (
    <form action={formAction}>
      <input name="title" required />
      <button type="submit">發布文章</button>
    </form>
  );
}

這個改動的意義:對於簡單的 CRUD 操作,你不再需要為了建立一個 Server Action 而建立一個獨立的 server 檔案。漸進式遷移變得更容易——可以先在 Client Component 內用 inline 'use server' 實驗,確認沒問題後再重構到獨立的 server 模組。


對開發效率的實際影響

React 19.2 的三個簡化,對日常開發的影響可以用三個場景說明:

場景React 19.0 的寫法React 19.2 的寫法
樂觀更新失敗回復需要自己寫 try/catch + 狀態回復React auto-remember 自動處理
跨元件共享表單狀態需要 Context 或 ZustanduseActionState(null, action) 直接共享
簡單 Server Action需要獨立 server 檔案 + 'use server' 頂層標記Client Component 內可用 'use server' inline

常見問題

Q:React 19.2 支援哪些版本?

React 19.2 需要 React 19.x 的環境。它不相容 React 18。如果你的專案還在 React 18,升級到 React 19 是使用這些功能的前提。

Q:auto-remember 適合所有場景嗎?

不完全是。auto-remember 適用於「簡單的單一狀態更新」。當你的 optimistic 更新涉及複雜的商業邏輯(例如庫存扣減需要同時更新多個地方的數字),手動管理仍然更安全。

Q:inline 'use server' 會影響效能嗎?

不會。'use server' 是一個靜態標記,它告訴 React 這個 function 需要在伺服器執行。即使寫在 Client Component 檔案裡,React 也會把它提取到獨立的 server bundle 中。


總結:React 19.2 的核心訊息

React 19.2 的主題是「讓框架處理繁瑣的事」:

  1. useOptimistic auto-remember:失敗回復不用自己寫
  2. useActionState 跨元件共享:狀態共享更直覺
  3. inline 'use server':簡單 Server Actions 不需要獨立檔案

這三個改動都指向同一個方向:框架吸收更多樣板程式碼,讓開發者專注在商業邏輯。對於每天都在寫 React 的工程師來說,這些「簡化」累積起來,就是實實在在的時間節省。


本文屬於「React 19 新功能」系列文章。