React前端開發JavaScript升級指南React 192026

React 19 升級完整指南:從 React 18 遷移要注意什麼

React 19 升級完整教學!React Compiler、Actions、useActionState 新特性詳解,含 Breaking Changes、MUI/Recoil 相容性問題與遷移步驟。前端工程師必看!

· 8 分鐘閱讀

React 19 是近年來最大的一次大版本更新。React Compiler 終於從實驗性變成穩定,Server Components 正式落地,Actions 把表單和非同步操作重新定義。但 Breaking Changes 也不少——MUI、Recoil、以及一些你可能用了多年的 API,都在不相容名單上。本文整理完整的升級決策框架,幫你評估「現在該不該升級」。


前端工程師為什麼要關心 React 19

React 18 發布快三年了。這三年裡社群對 React 的抱怨主要集中在幾個地方:useEffect 的複雜性、手動 memoization(useMemouseCallback)的負擔、Server Components 的模糊地帶。

React 19 試圖一次解決這三個問題。

但升級從來不是無痛的。React 19 的 Breaking Changes 波及了多個流行第三方庫(Recoil 完全不相容,MUI 需要特定版本),而 React Compiler 的穩定化,意味著你需要重新思考「什麼時候該相信 compiler,什麼時候還是要手動優化」。

如果你負責一個 React 專案的升級決策,這篇文章提供一個框架,幫你評估:

  1. React 19 的新特性哪個對你的團隊最有價值
  2. 現有程式碼需要改多少
  3. 第三方依賴的相容性怎麼處理

React 19 的核心新特性

1. React Compiler:讓你自己決定什麼時候 trust it

React Compiler(之前叫 React Forget)是這次更新最受矚目的功能。它的核心作用是:自動分析元件的 JSX,在編譯時插入必要的 memoization 語句,讓開發者不再需要手動寫 useMemouseCallback

// 過去:需要手動判斷什麼時候 memoize
function ExpensiveList({ items, filter }) {
  const filtered = useMemo(
    () => items.filter(item => item.category === filter),
    [items, filter]
  );

  return filtered.map(item => <ListItem key={item.id} item={item} />);
}

// React Compiler 之後:compiler 自動幫你分析並插入 memoization
// 你只需要寫:
function ExpensiveList({ items, filter }) {
  const filtered = items.filter(item => item.category === filter);
  return filtered.map(item => <ListItem key={item.id} item={item} />);
}

Compiler 的工作方式

  • 它追蹤 JSX 內所有表達式的值流動(value flow)
  • 當它發現一個值可能來自 props 或 state,會自動插入 memoization
  • 結果基本上等同於你手動加了 React.memo + useMemo + useCallback

限制:不是所有程式碼都會被優化

React Compiler 有一個「規則」:如果你的程式碼違反了 React 的 purity(純度)規則,Compiler 會跳過那個元件。所以:

// ❌ 這個不會被優化(違反 purity)
function BadComponent({ items }) {
  const total = items.reduce((sum, item) => sum + item.price, 0);
  console.log('total:', total); // side effect,Compiler 跳過
  return <span>{total}</span>;
}

// ✅ 這個會被優化
function GoodComponent({ items }) {
  const total = items.reduce((sum, item) => sum + item.price, 0);
  return <span>{total}</span>;
}

實務建議

  • 不要急著刪掉所有 useMemo/useCallback。Compiler 會幫你做這件事,但你需要先讓它運作起來、觀察它的行為,再逐步移除手動 memoization。
  • 當你遇到效能問題時,先看 Compiler 的 output,再決定要不要干預。

2. Actions:表單和非同步操作的統一處理方式

React 19 的 Actions 為表單提交(form submission)和非同步操作提供了一個宣告式模型。

// actions.js
'use server';

async function submitForm(prevState, formData) {
  // 這個函式在伺服器端執行
  const email = formData.get('email');
  const result = await saveToDatabase({ email });

  if (!result.success) {
    return { error: result.message };
  }

  return { success: true };
}
// Form.jsx
'use client';

import { useFormState } from 'react-dom'; // 從 react-dom 匯入
import { submitForm } from './actions';

function Form() {
  const [state, formAction] = useFormState(submitForm, null);

  return (
    <form action={formAction}>
      <input type="email" name="email" required />
      {state?.error && <p className="error">{state.error}</p>}
      {state?.success && <p className="success">提交成功!</p>}
      <button type="submit">送出</button>
    </form>
  );
}

useFormStateuseActionState(React 19 已 deprecated)

// React 19:使用新版 hook
import { useActionState } from 'react';

async function submitForm(prevState, formData) {
  // ...
}

// useActionState 比 useFormState 更簡潔
const [state, formAction, isPending] = useActionState(submitForm, null);

useFormStatus:在 Action 執行時取得表單狀態

import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? '提交中...' : '送出'}
    </button>
  );
}

3. use():在渲染中讀取 Promise 和 Context

use() 是 React 19 新增的 Hook,它允許你在元件的渲染階段(render phase)讀取 Promise 或 Context 的值——這在以前是不可能的。

import { use, Suspense } from 'react';

function UserProfile({ userPromise }) {
  // ✅ use() 允許在 render 中讀取 Promise
  const user = use(userPromise);

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
    </div>
  );
}

// 使用時包在 Suspense 外面
<Suspense fallback={<div>載入中...</div>}>
  <UserProfile userPromise={fetchUser(userId)} />
</Suspense>

use() vs useEffect + State:什麼時候用哪個

情境建議
需要 render 時同步取得值use()(搭配 Suspense)
需要在 DOM 更新後做副作用useEffect
想在 render 時非同步載入資料use() + Suspense
想要 loading / error 狀態use() + ErrorBoundary
// ❌ 過去的錯誤做法:render 時 throw Promise(這會觸發 Suspense boundary)
function BadUserProfile({ userPromise }) {
  const user = userPromise; // 沒有 await,會 throw
  return <div>{user.name}</div>;
}

// ✅ 正確做法:用 use() 並包在 Suspense
function GoodUserProfile({ userPromise }) {
  const user = use(userPromise);
  return <div>{user.name}</div>;
}

4. useOptimistic:樂觀更新

useOptimistic 讓你實作「樂觀 UI」(optimistic UI)——在非同步操作完成前,先顯示預期的結果,給用戶即時反饋。

import { useOptimistic, useState } from 'react';
import { sendMessage } from './actions';

function ChatRoom({ messages, roomId }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) => [
      ...state,
      { id: Date.now(), text: newMessage, pending: true }
    ]
  );

  async function handleSend(formData) {
    const text = formData.get('message');
    addOptimisticMessage(text); // 立即顯示,不用等 server 回應

    await sendMessage({ roomId, text });
    // 如果失敗,React 會自動 revert 這個 optimistic update
  }

  return (
    <div>
      {optimisticMessages.map(msg => (
        <div key={msg.id} className={msg.pending ? 'pending' : ''}>
          {msg.text}
        </div>
      ))}
      <form action={handleSend}>
        <input name="message" />
      </form>
    </div>
  );
}

Breaking Changes:升級前必須處理的問題

1. Deprecated APIs 的移除

React 19 移除了多個 Deprecated API:

// ❌ 這些在 React 19 中已不存在
import { createFactory } from 'react';       // 已移除
import { useFormState } from 'react-dom';   // 改名為 useActionState
import { render } from 'react-dom';          // 已移除(用 createRoot)

rendercreateRoot(React 18 就已經 deprecated)

// React 18+ 標準寫法
import { createRoot } from 'react-dom/client';

const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);

2. useFormStateuseActionState 遷移

// React 18(deprecated)
import { useFormState } from 'react-dom';

const [state, formAction] = useFormState(submitForm, initialState);
// React 19(新版)
import { useActionState } from 'react';

const [state, formAction, isPending] = useActionState(submitForm, initialState);
// useActionState 多了 isPending,更方便

3. Recoil 完全不相容

這是一個大問題。如果你還在用 Recoil(Facebook 出的狀態管理庫),React 19 不支援。建議的遷移路徑:

從 Recoil 遷移到適用場景
Zustand簡單、輕量、最流行
JotaiAtomic model,接近 Recoil 概念
React Context + useReducer簡單全域狀態,不需要外部庫
Redux Toolkit複雜應用,需要 DevTools
// Recoil → Zustand 範例
// Recoil
const countState = atom({ key: 'count', default: 0 });
const [count, setCount] = useRecoilState(countState);

// Zustand
import { create } from 'zustand';

const useCountStore = create((set) => ({
  count: 0,
  setCount: (count) => set({ count }),
}));

function Counter() {
  const { count, setCount } = useCountStore();
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

4. MUI(Material-UI)版本需求

MUI 在 React 19 需要 v5.15+ 才完全相容。如果你還在 MUI v4,需要先升級 MUI,再升級 React。

# 建議升級路徑
1. npm install @mui/material@5 @emotion/react @emotion/styled
2. 處理 MUI v4 v5 breaking changes(CSS-in-JS API 變化)
3. npm install react@19 react-dom@19
4. 運行 React 19 codemod

遷移步驟:實際的升級流程

Step 1:執行 React Upgrade Tool

npx react-upgrade

# 或

npx @react-labs/react-19-codemod@latest

這個工具會自動幫你处理大部分的 API 改動,但複雜的邏輯還是需要手動處理。

Step 2:更新相依套件

# 檢查所有 React 相依套件的相容性
npm ls | grep react

# 建議更新清單(如果有用這些庫):
npm install @mui/material@latest @emotion/react@latest recharts@latest
# 不要更新 Recoil——直接替換成 Zustand/Jotai

Step 3:處理 Breaking Changes

// React 19 的 breaking changes 檢查清單:
// 1. useFormState → useActionState
// 2. render() → createRoot()
// 3. createFactory → 移除,用 createElement 代替
// 4. Legacy Context → React.createContext(這個沒變,只是提醒)

Step 4:啟用 React Compiler(可選)

# 安裝 babel plugin(如果你用 Babel)
npm install @babel/plugin-react-compiler

# babel.config.js
{
  "plugins": [
    ["@babel/plugin-react-compiler", {
      "target": "19"
    }]
  ]
}

Step 5:Server Components(如果你用 Next.js)

// Next.js App Router + React 19:Server Components 正式穩定
// app/page.tsx — 這個元件預設在伺服器端執行
async function Page() {
  // 可以直接 await,不用 useEffect + useState + use()
  const data = await fetch('/api/data').then(r => r.json());

  return <div>{data.map(item => <p key={item.id}>{item.name}</p>)}</div>;
}

升級決策框架

什麼時候應該現在升級

符合以下條件,現在升級是合理選擇:

  • 你的應用已經是 React 18 + Next.js 14/15
  • 你的團隊有興趣使用 React Compiler 來減少 memoization code
  • 你的第三方庫(MUI、Emotion 等)都已經有 React 19 相容版本
  • 你的應用主要是新功能開發,沒有太多 legacy 技術債

什麼時候應該觀望

符合以下條件,建議再等 3-6 個月:

  • 你還在用 Recoil(需要先遷移到 Zustand/Jotai,再升級 React)
  • 你的應用有大量自定義的 useEffect 邏輯(需要重構才能充分發揮新 API 價值)
  • 你的團隊正在忙碌的 sprint 中,升級風險高
  • 你的產品馬上要發布大版本,不適合這個時間點折騰

結語

React 19 的新特性不是全部都需要立刻學習的。

最值得投資的是:

  1. React Compiler——它會根本性地改變你寫元件的方式,長遠價值最高
  2. useActionState——比 useFormState 更好用,遷移成本低
  3. useOptimistic——樂觀更新是很好的 UX pattern,現在可以用原生方式做了

Breaking Changes 裡,Recoil 的不相容是最大的坑。如果你的團隊現在還用 Recoil,先把狀態管理庫遷移到 Zustand,再考慮升級 React。否則,你會發現升級完之後一堆元件壞掉,不知道是 React 的問題還是 Recoil 的問題。


延伸閱讀


本文是「2026 前端框架選擇」系列文章之一。如果你正在規劃 React 升級,有任何具體問題,歡迎留言。