React Compiler 自動 Memoization 優化效能教學:告別手動 useMemo 的時代
老實說,我寫 React 這幾年,最煩的事情之一就是到處加 useMemo、useCallback 和 React.memo。每次 code review 都在討論「這邊要不要 memo」、「dependency array 少了一個值」,浪費大量時間在這種機械式的效能優化上。React Compiler 的出現,終於讓我們可以把這些瑣事交給編譯器處理了。
這篇文章會帶你從頭了解 React Compiler 的運作原理,到實際在專案中啟用它,以及那些你該注意的邊界情況。
React Compiler 是什麼?React 團隊為什麼要做這個?
React Compiler(原本叫 React Forget)是 React 團隊從 2021 年就開始研發的編譯器工具。它的核心目標很簡單:自動幫你的 React 元件加上 memoization,讓你不用再手動寫 useMemo、useCallback 這些 hook。
為什麼 React 團隊覺得這件事很重要?因為他們發現大部分的 React 效能問題都來自「不必要的重新渲染」。理論上,開發者應該用 useMemo 和 React.memo 來避免這些渲染,但現實是:
- 很多開發者根本不知道什麼時候該加 memo
- 加太多 memo 反而會增加記憶體開銷
- dependency array 寫錯比不加 memo 還可怕
- 團隊中每個人對 memo 策略的理解不一致
React Compiler 的解法是在 build time 分析你的程式碼,自動判斷哪些值需要被快取、哪些元件需要避免重新渲染,然後在編譯階段就把優化加進去。
過去手動 useMemo/useCallback/memo 的痛點
在 React Compiler 出現之前,我們每天都在跟這些問題搏鬥:
1. Dependency Array 地獄
// 你有沒有寫過這種東西?
const filteredItems = useMemo(() => {
return items.filter(item =>
item.category === selectedCategory &&
item.price >= minPrice &&
item.name.includes(searchTerm)
);
}, [items, selectedCategory, minPrice, searchTerm]);
// 少放一個 dependency?恭喜你拿到一個超難 debug 的 bugESLint 的 exhaustive-deps 規則可以幫忙抓,但它有時候會建議你加一些不該加的依賴,導致 memo 完全失效。
2. useCallback 到處都是
一旦你開始 memo 子元件,你就得把傳給它的每個 callback 都用 useCallback 包起來。一個元件可能有五六個 handler,每個都要包,程式碼馬上變得又臭又長。
3. 效能優化變成技術債
最諷刺的是,手動 memo 常常變成技術債。因為需求改了、props 變了,但 dependency array 沒跟著更新,就會產生微妙的 bug。我見過好幾次因為 useMemo 的 dependency 沒更新,導致 UI 顯示舊資料的問題。
React Compiler 如何自動分析元件並做 Memoization
React Compiler 的運作原理相當精巧。它在 build time 做了這幾件事:
靜態分析階段
Compiler 會解析你的 React 元件,建立一個依賴關係圖。它會追蹤每個變數、每個 props、每個 state 之間的關係,判斷哪些計算結果依賴於哪些輸入。
自動插入快取邏輯
根據分析結果,Compiler 會自動在適當的位置插入快取程式碼。這等同於它幫你加了 useMemo、useCallback 和 React.memo,但比人工判斷更精準。
// 你寫的程式碼
function ProductList({ products, category }) {
const filtered = products.filter(p => p.category === category);
const total = filtered.reduce((sum, p) => sum + p.price, 0);
return (
<div>
<p>Total: {total}</p>
{filtered.map(p => <ProductCard key={p.id} product={p} />)}
</div>
);
}
// Compiler 編譯後的效果(概念上)
// filtered 和 total 只在 products 或 category 變化時重新計算
// ProductCard 的 props 沒變就不會重新渲染遵守 React 的規則
Compiler 假設你的程式碼遵守「Rules of React」——也就是元件是純函式、不在 render 中產生副作用、props 和 state 是 immutable 的。如果你的程式碼違反這些規則,Compiler 會跳過不優化,而不是產生錯誤的結果。
實際效能提升數據與案例
根據 Meta 內部在 Instagram 和 Facebook 上的測試數據,React Compiler 帶來的效能提升相當顯著:
- 互動反應速度提升了 5% 至 12%,某些複雜頁面甚至到 15%
- 不必要的重新渲染減少了 40% 以上
- JavaScript bundle 的執行效率明顯改善,因為 Compiler 比人工更精準地判斷哪些需要快取
我自己在一個中型的電商專案試過,最明顯的改善是在商品列表頁。原本捲動時會有明顯的卡頓(因為篩選邏輯每次都重新執行),啟用 Compiler 後完全感覺不到了。
不過老實說,如果你的專案本來就很小、元件很簡單,Compiler 帶來的提升可能不明顯。它最大的價值其實是在大型專案和複雜 UI 上。
如何在 React 19 專案中啟用 Compiler
在 React 19 中啟用 Compiler 其實不難。以下是 step-by-step 的步驟:
步驟一:安裝 Babel 外掛
npm install babel-plugin-react-compiler步驟二:設定 Babel 或 bundler
如果你用 Vite:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: [
['babel-plugin-react-compiler', {}]
],
},
}),
],
});如果你用 Next.js 15+,在 next.config.js 加上:
// next.config.js
module.exports = {
experimental: {
reactCompiler: true,
},
};步驟三:驗證 Compiler 是否運作
在 React DevTools 中,被 Compiler 優化過的元件會顯示一個「Memo ✨」標記。你也可以用 eslint-plugin-react-compiler 來檢查哪些元件被成功優化。
npm install eslint-plugin-react-compiler在搭配 Astro 5 Islands Architecture 或其他框架時,確保 Compiler 的 Babel 外掛正確載入在 React 元件的編譯流程中。
注意事項:哪些 Pattern 可能不被 Compiler 優化
React Compiler 不是萬能的。以下幾種 pattern 它可能會選擇不優化:
1. 違反 Rules of React 的程式碼
// 這段不會被優化,因為 render 中有副作用
function BadComponent({ userId }) {
// 直接在 render 中修改外部變數
globalCounter++;
return <div>{userId}</div>;
}2. 動態 property access
如果你的程式碼大量使用動態 key 存取物件,Compiler 可能無法追蹤依賴關係。
3. 複雜的 class component
React Compiler 主要針對 function component 設計。如果你還在用 class component,建議逐步遷移。
4. 非標準的 hook 用法
自訂 hook 如果不遵守命名規範(以 use 開頭),Compiler 可能無法正確分析。
另外,如果你的專案使用 Bun Shell 腳本開發 作為建構工具,請確認 Babel 外掛的相容性,因為 Bun 的 transpiler 行為可能與 Webpack/Vite 略有不同。
搭配 Server Components 的最佳實踐
React Compiler 和 Server Components 是兩個互補的效能優化策略。以下是我建議的搭配方式:
Server Components 不需要 Compiler
Server Components 在伺服器端渲染,不會在客戶端重新渲染,所以 memoization 對它們沒有意義。React Compiler 會自動跳過 Server Components。
Client Components 全部交給 Compiler
把 'use client' 的元件交給 Compiler 處理就對了。特別是那些接收大量 props、有複雜計算邏輯的互動式元件,Compiler 的優化效果最好。
建議的架構
// Server Component - 不需要 memo
async function ProductPage({ id }) {
const product = await fetchProduct(id);
return (
<div>
<h1>{product.name}</h1>
{/* Client Component - Compiler 會自動優化 */}
<ProductInteractions product={product} />
<ReviewSection productId={id} />
</div>
);
}
// Client Component - Compiler 自動處理 memoization
'use client';
function ProductInteractions({ product }) {
const [quantity, setQuantity] = useState(1);
const totalPrice = product.price * quantity; // Compiler 會自動 memo
return (
<div>
<p>單價:${product.price}</p>
<p>小計:${totalPrice}</p>
<QuantitySelector value={quantity} onChange={setQuantity} />
</div>
);
}結語:擁抱自動化,專注在業務邏輯
React Compiler 代表了前端開發的一個重要轉折:效能優化不再是開發者的責任,而是編譯器的工作。就像 Garbage Collection 讓我們不用手動管理記憶體一樣,React Compiler 讓我們不用再手動管理 memoization。
我的建議是:
- 新專案直接啟用 React Compiler,從第一天就享受自動優化
- 舊專案可以漸進式採用,Compiler 的設計允許你逐個檔案啟用
- 趁這個機會清理掉手動的
useMemo和useCallback,讓程式碼更乾淨 - 把省下來的時間花在真正重要的事情上——商業邏輯和使用者體驗
React Compiler 讓我們離「只寫業務邏輯,其他交給工具」的理想又近了一步。如果你還沒試過,真的該動手了。
繼續閱讀
TanStack Query v5 完整教學:React 伺服器狀態管理與資料快取最佳實踐
相關文章
你可能也喜歡
探索其他領域的精選好文