📋 目錄
React 19 是近年來最大的一次大版本更新。React Compiler 終於從實驗性變成穩定,Server Components 正式落地,Actions 把表單和非同步操作重新定義。但 Breaking Changes 也不少——MUI、Recoil、以及一些你可能用了多年的 API,都在不相容名單上。本文整理完整的升級決策框架,幫你評估「現在該不該升級」。
前端工程師為什麼要關心 React 19
React 18 發布快三年了。這三年裡社群對 React 的抱怨主要集中在幾個地方:useEffect 的複雜性、手動 memoization(useMemo、useCallback)的負擔、Server Components 的模糊地帶。
React 19 試圖一次解決這三個問題。
但升級從來不是無痛的。React 19 的 Breaking Changes 波及了多個流行第三方庫(Recoil 完全不相容,MUI 需要特定版本),而 React Compiler 的穩定化,意味著你需要重新思考「什麼時候該相信 compiler,什麼時候還是要手動優化」。
如果你負責一個 React 專案的升級決策,這篇文章提供一個框架,幫你評估:
- React 19 的新特性哪個對你的團隊最有價值
- 現有程式碼需要改多少
- 第三方依賴的相容性怎麼處理
React 19 的核心新特性
1. React Compiler:讓你自己決定什麼時候 trust it
React Compiler(之前叫 React Forget)是這次更新最受矚目的功能。它的核心作用是:自動分析元件的 JSX,在編譯時插入必要的 memoization 語句,讓開發者不再需要手動寫 useMemo 和 useCallback。
// 過去:需要手動判斷什麼時候 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>
);
}
useFormState → useActionState(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)
render → createRoot(React 18 就已經 deprecated)
// React 18+ 標準寫法
import { createRoot } from 'react-dom/client';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
2. useFormState → useActionState 遷移
// 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 | 簡單、輕量、最流行 |
| Jotai | Atomic 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 的新特性不是全部都需要立刻學習的。
最值得投資的是:
- React Compiler——它會根本性地改變你寫元件的方式,長遠價值最高
- useActionState——比 useFormState 更好用,遷移成本低
- useOptimistic——樂觀更新是很好的 UX pattern,現在可以用原生方式做了
Breaking Changes 裡,Recoil 的不相容是最大的坑。如果你的團隊現在還用 Recoil,先把狀態管理庫遷移到 Zustand,再考慮升級 React。否則,你會發現升級完之後一堆元件壞掉,不知道是 React 的問題還是 Recoil 的問題。
延伸閱讀
- React Server Components 完整指南 — RSC 與 React 19 的整合實戰
- 2026 前端框架比較:React、Vue、Svelte 誰與爭鋒 — 框架選型參考
本文是「2026 前端框架選擇」系列文章之一。如果你正在規劃 React 升級,有任何具體問題,歡迎留言。