React Compiler 正式發佈!
一次了解你需要知道的全部重點
Zet@JSDC 2025
2025.11.29


-
iCHEF 主任前端工程師
-
近 12 年前端開發經驗,11 年 React 開發經經驗
-
曾任 SITCON、JSDC、WebConf Taiwan 主議程講者
-
第 14 屆 IThome 鐵人賽 Modern Web 組冠軍
Zet
周昱安
About me
為什麼 React 需要一個 Compiler
Source-to-source compiler
-
Source-to-source compiler 是一種將程式原始碼轉換成另一種模樣的程式原始碼的轉譯工具,無論是同語言或是跨語言的轉換。所以你可以想像成是「以工具自動化的修改並替換你寫的程式原始碼中的部分片段」。
-
舉例來說,像是 TS compiler 能將 TypeScript 程式碼轉譯成普通的 JavaScript 程式碼,或是 JSX transformer 能將 React 的 JSX 標籤語法轉換成
React.createElement()/_jsx()語法 -
透過使用 transpiler,開發人員可以在不同平台上重用程式碼、自定義語法糖、進行程式碼的靜態優化,並在引入新技術或語言的新特性時,更輕鬆的實現向下相容。

React Compiler
- React 本身已經有堪用的內建自動效能優化機制,然而在一起比較極端或效能敏感的情境下,你可能會需要手動的添加一些 memoization 處理來進一步改善 React app 的效能,例如
memo()、useCallback()、useMemo() - 然而人工手動維護這些 memoization 的關係鏈是一件非常繁瑣且容易遺漏出錯的事情,如果沒處理好的話可能效能甚至比沒寫效能優化時更糟糕
- React compiler 主要的目標就是靜態分析你的 React 程式碼,並完全自動化的修改你的原始碼來添加正確、安全的 memoization 效能優化


useCallback()
memo()


useMemo()


Before React Compiler


After React Compiler

React Compiler 的發展沿革
Prepack

React Forget (2021)

React Forget (2021)

React Compiler (2024)

React Compiler v1.0 (2025.10)

React Compiler 的效果
自動 memoization(render 等級優化)
- 自動抓依賴、幫你做 memoization
- Compiler 會在 build time 讀你的元件/ hooks 程式碼,分析 data-flow 與 mutability,自動決定哪些值要 cache、依賴是什麼
-
目標是讓你幾乎不用再手寫:
-
useMemo -
useCallback -
React.memo
這些都變成「逃生門 / 特殊控制」,而不是日常必備套路
-
自動 memoization(render 等級優化)
- 它能比你做得更激進、更準的地方
-
細粒度 memo:不是直接
memo()整個 component,而是自動分析 component 函數中資料與 UI 之間的依賴關係,分別記憶某些中間變數、計算結果、UI 結果(React element) -
跨條件路徑:就算你在 component 程式碼的
return之後才做變數計算,它還是能在後面那段做 memo(手寫useMemo很難覆蓋這種 pattern,因為 hooks 不可以放置在任何 return 後面) - 更聰明的依賴推導:v1.0 針對像 optional chaining、array index 之類的依賴判斷有優化,減少不必要 re-render
-
細粒度 memo:不是直接
細粒度 memoization
-
React compiler 並不會直接做出等價於
React.memo() 函數的效能優化-
React.memo()會透過比較傳入的所有 props 是否都相同(或你可以自行指定areEqual 函數),來決定是否直接跳過該 component 的 render phase 並回傳之前已記憶的畫面 render 結果- 這種優化的粒度單位是「整個 component」
-
React compiler 則不同,它會針對每一個作用的 component 都進行細節的分析,根據 component 中的「資料 => UI 」對應關係將畫面渲染流程切成不同的區塊,並且分別進行 memoization 效果優化
- 這種優化的粒度單位是「資料 => UI 形成的依賴關係」,一個 component 中可能有好幾段這種關係
- 改成細粒度 memo 的好處,是把「效能最佳化」從 component 邊界的手工設計,變成「以資料依賴為中心、可組合、可演進」的自動化行為
- React compiler playground demo
-
不再被「component 切多細」綁死效能優化的設計
-
傳統
React.memo()/ component 粒度優化有個老問題:-
你想優化好效能,就常常被迫為了 memo 而拆 component:
-
把
<Header />、<List />、<Footer />拆到處都是,只是為了在每顆外面包一層React.memo -
有時候某段 JSX 邏輯上明明應該跟旁邊的東西直接放在一起會有更好的可讀性與可維護性,卻被拆出去只是因為「這塊不該頻繁 re-render,需要 memo」
-
-
這會導致 component 的設計邊界同時承擔兩種壓力:
-
UI / domain 功能邏輯上的邊界
-
效能上的 cache 邊界
-
-
-
而 React Compiler 自動做細粒度 memo 的好處就是:
-
component 還是照你 UI / domain 功能邏輯的思考去切(易懂、好維護)
-
「這塊要不要重算」變成 component 內部的細節,由 compiler 根據依賴分析決定
→ 你不必為了效能把 UI tree 切成一堆碎 component 再到處加React.memo()
-
避免「一刀切」式 memo 帶來的錯判與脆弱性
-
React.memo天生有一些明顯的問題-
props reference 每次都變(例如 inline object / function)時,
React.memo就會完全失效,然後你就開始全專案滿地useMemo/useCallback來配合它 -
這種 render 的效能優化僅能作用於父 component re-render 時,而 component 自身內部 setState 時則無法享受到效能優化的好處
-
-
細粒度 memo 把這個問題拆解掉:
-
比對依賴的是每個 sub-expression 真正用到的值,而不是 props 的值
-
compiler 會自己分析資料流的依賴關係:
-
哪些 props / state / context 會影響這個 JSX / callback /中間值,只分別針對那一小撮依賴做 cache
-
-
即使是 component 自己因 setState 而導致的 re-render,也能夠享受到細粒度的效能優化
-
-
結論就是:
-
效能行為不再緊耦合在「props 是否更新、props 長什麼樣子」上,而是綁在「實際上渲染結果有用到哪些 reactive source data」上
-
你 refactor props 形狀、拆合 component,compiler 重新分析一遍,cache 行為會跟著更新,比
React.memo靠人維護安全很多
-
可以優化到「你不會自己動手 memo 的地方」
-
開發者手動
React.memo/useMemo通常只會加在:-
「肉眼看起來很花時間」的計算
-
「直覺上覺得 re-render 頻率會很兇」的 component
-
-
但其實很多微小的東西人類通常不會特別去 memo,例如:
-
一個看起來很小的 object literal,被當作 options 傳給第三方 lib,每改就重建 expensive instance
-
inline arrow function
-
一段 JSX sub-tree 雖然「小」,但在一個 render-heavy 的列表裡被大量重複
-
-
細粒度 memo 的特點是:
-
Compiler 不會嫌小:任何有依賴的中間值,都可以被分配 cache slot
-
這種「以人類角度不會特別去優化的地方」,compiler 反而可以系統性處理
-
對大型 UI 來說,這種「很多很小的 cache」累積起來,效益其實蠻可觀,而且你不用多寫一行 code。
-
邏輯條件複雜時能帶來比手寫安全又乾淨的 memo
-
一旦 component 裡有複雜的條件式或控制流程(if/else、多個 return),你如果要:
- 手動用 useMemo
- 又要正確處理所有分支的依賴
- 又要維持程式碼可讀性
-
很快就會變成:
-
useMemo跟一堆條件拆開(尤其 hooks 被要求要寫在任何 return 前面),程式變得很不直觀 - 或依賴漏寫、寫錯,得到奇怪的 bug
- React compiler playground
-
-
Compiler 可以把這些「看起來分支很多的路徑」統一當作一個 reactive 值,幫它分配 memo slot
-
你不用費心去想:「這個東西要不要包一個 useMemo,那依賴要寫哪幾個?」
-
換句話說:細粒度 memo 讓你可以先寫「好懂的邏輯」,由 compiler 來負責在不破壞語義的前提下做 aggressive 的 cache,而傳統 component-level memo 常常倒過來:為了做效能優化只好寫「不好懂的邏輯」。
-
對長期維護與重構更加友善
-
component-level memo 最大的副作用就是:它會長成「跨 component、甚至是專案層級大型最佳化決策」:
-
你一旦加了
React.memo(Foo):- 之後只要改 props/state 結構,都要心裡默念「這個 component 是有
React.memo過的,我要小心不要弄壞它」
- 之後只要改 props/state 結構,都要心裡默念「這個 component 是有
-
而對新來的人來說,專案中每個
React.memo都是跨 componet 的未知危險禁區:- 不確定為什麼當初加
React.memo - 不知道
React.memo能不能安全拿掉 - 改壞了行爲或效能優化也不確定是哪個環節出了問題,或許甚至是某個父層 component 導致的
- 不確定為什麼當初加
-
你一旦加了
-
細粒度 memo 把這個「跨 component 的巨大優化決策」收斂成一堆局部的小決策,而且是 compiler 自動做的:
- 你 refactor component 內部實作:換個寫法、拆 function、合併 JSX…,然後 compiler 重新分析一次依賴關係,內部 cache slot 自己重排
- 對你來說,效能最佳化變成「隱含在 React 語言背後的一致行為」,而不是一堆你手寫的微妙 memo hack
-
長期下來:
- code review 不用一直在那邊算「這顆 React.memo 到底還有沒有用」
- 也比較不會出現「某人刪掉某一個手動 memo 的處理,效能優化就一夜回到解放前」這種驚喜
如果 React compiler 設計成無差別套用 React.memo?
-
如果 compiler「偷偷幫你所有 component 套
React.memo」,實務上的 DX 問題會很嚴重 -
你很難知道「為什麼這個 component 這次不 re-render」
-
props 明明看起來有變(例如物件內部的某個欄位值變了),但 reference 沒變 →
React.memo覺得「一樣」,子 component 不 re-render。 -
你在 DevTools 看到父 component 每次都有 re-render,子 component 卻靜止不動
-
-
你不知道「哪裡真的有 memo,哪裡沒有」
-
如果是你自己手寫的
React.memo,至少打開檔案就看得到。 -
如果是 compiler 偷偷加,你得去學一套「黑盒子規則」:什麼時機改得到、什麼時候不會 re-render,學習成本很高
-
-
心智模型跟既有 React 普遍認知不對齊
-
現在的心智模型:
-
component 預設是「每次父層 re-render 都會跑」,
-
只有你明確使用
React.memo才改變行為
-
-
如果 compiler 無差別 memo:
-
「預設行為」就被悄悄改成:可能會跳過 render
-
這反而另開發者產生另一種心智負擔,抵觸了 React compiler 想要改善心智負擔的初衷
-
-
-
React Compiler 現在的策略是:「程式語意不變,只優化內部實作」:
-
你不必把 compiler「有沒有 memo」納入日常心智模型的一部分
-
你還是可以假設:component function 每次 render 都會進來跑「一圈邏輯」,只是裡面有些值會被自動 cache
-
如果它改成「全自動
React.memo」,就違反了這條設計準則
-
總結:你可以怎麼替換心智模型
- 總結來說:
-
舊世界:
-
React.memo= 在 component 外面加一個「大閘門」,全靠 props 控制 re-render -
當真的發生 re-render 時,透過手動加上的
useCallback / useMemo來維護資料流的依賴鏈,讓旗下的效能優化能夠正常運作
-
-
新世界:
-
React Compiler = 每個 component 裡面都有一個「依賴追蹤+ cache 系統」,幫你對所有衍生值做細粒度更新
-
-
所以改用 compiler 自動做細粒度 memo 的主要好處就是:
-
component 的切分設計可以回到以「UI / domain 邏輯邊界」為主,而不是以配合
React.memo的需求為優先 -
讓很多人類不會手動優化的小地方,一起被涵蓋
-
跟複雜邏輯(條件式、多個 return)共存時,可讀性跟正確性更有保障
-
對於長期的可維護性更友善,不用考慮一堆歷史的 memo 決策
-
-
使用 React compiler
React Compiler 的支援概況
-
版本狀態:React Compiler v1.0 正式版,2025/10 公布,API 承諾向後相容,避免再有大的 breaking changes
-
工具定位:不是 React 19 內建的一部分,而是額外安裝的編譯工具(Babel plugin)
-
React 版本相容性
-
最佳搭配版本:React 19
-
React 19 內建 compiler runtime API,直接吃編譯後輸出,效能 & 整合最佳
-
-
支援 React 17 / 18(舊專案):
-
需安裝
react-compiler-runtimepolyfill -
在 compiler 設定裡指定
target: '17' | '18',讓輸出碼相容舊版 React
-
-
官方文件明講:Compiler 支援 React 17、18、19,但「設計上以 19 為優先」。
-
-
目標平台支援
-
Web(React DOM)
-
一般 React SPA / CSR / SSR / Next.js app 都可使用,只要 build pipeline 有接上 Babel plugin。
-
-
React Native / Expo
-
官方與社群實戰都已支援 React Native
-
建議版本:React Native 0.80+,Expo SDK 54+,Expo 文件已提供專章講 React Compiler
-
-
-
Build Tool / 框架支援概況
-
Babel 為核心入口:目前官方提供的是
babel-plugin-react-compiler。 -
已有明確整合文件或官方範例的工具/框架:
-
Vite / Astro / Next.js
-
Rspack / Rsbuild / Modern.js 等新一代 bundler
-
Expo / React Native
-
-
安裝 React Compiler
-
React Compiler 目前以 Babel plugin 的形式發布:
babel-plugin-react-compiler-
必須在 Babel 中作為最先執行的 plugin(放在
plugins陣列最前面),因為它需要看到「最初版本的原始程式碼」才能正確進行分析 -
一般 Babel preset(
@babel/preset-react、@babel/preset-typescript等)可以照常使用,React compiler 只負責額外的優化轉換
-
-
更多資訊可以參考官方文件:https://react.dev/learn/react-compiler/installation


React Compiler 的配套 linter plugin
-
React Compiler 僅會針對「符合 React 規則要求」的 component 進行自動的 memoization 優化,若不符合則會跳過
-
因此 React 官方也更新了 eslint-plugin-react-hooks,添加更多 compiler 需求的規則檢查,以便於開發者能夠更容易的撰寫符合 compiler 要求的 React 程式碼
-
現在不用另外裝 compiler 專屬的 ESLint plugin,只要裝最新版
eslint-plugin-react-hooks+recommended-latestpreset 就會同時吃到 Hooks 規則+ Compiler 規則
-
React Compiler 的配套 linter plugin
-
Compiler 專屬/相容性相關規則:讓 compiler 能看懂、敢安全的優化
-
purity & immutability
-
purity:禁止在 render 呼叫已知的 impure function(例如Math.random(),Date.now())。 -
immutability:避免直接 mutate props / state,以保持資料流的 immutable
-
-
不安全的 setState / ref 使用
-
set-state-in-render:防止在 render 裡直接 setState(會造成無限 re-render)。 -
set-state-in-effect:警告在 effect 開頭同步 setState,造成多一次不必要 render。 -
refs:禁止在 render 中讀寫ref.current,避免不穩定的行為。
-
-
語法與第三方相容性
-
unsupported-syntax:禁止eval、with等 Compiler 無法靜態分析的語法。 -
incompatible-library:偵測已知跟 React / compiler 模型不相容的 library API(例如會偷偷 mutate 的表單/表格工具),讓 compiler 自動跳過這些 component。
-
-
其它與 compiler 緊密相關的規則
-
static-components、use-memo、preserve-manual-memoization:協助你在有手動 memo 的地方跟 compiler 協作,避免互相打架 -
config、gating:檢查babel-plugin-react-compiler的設定有沒有打錯 key / 填錯值
-
-
Live demo
-
npm create vite@latest my-app -- --template react -
npm install -D babel-plugin-react-compiler@latest -
https://react.dev/learn/react-compiler/installation#eslint-integration
將 React Compiler 導入既有的 React 專案
漸進式導入 React Compiler
-
為什麼既有專案適合採用「漸進式導入」
-
不用一次全開
-
React Compiler 本來是設計給整個 codebase 自動優化用,但官方明講:對既有專案,建議先只處理一小塊,確認穩定再慢慢擴大
-
-
可以先驗證「有沒有變慢/變壞」
-
小範圍啟用 → 跑測試+量效能 → 觀察有沒有奇怪 re-render / bug,再決定要不要放大範圍
-
-
違反 Rules of React 的舊碼,可以分批修理
-
Compiler + ESLint 會幫你找到不純 render、錯的 hooks 用法等等,漸進式導入可以讓你不用一次修好全 codebase,而是每擴一個區塊就順手整理那一塊
-
-
方便做 A/B test 或分階段上線
-
你可以只對某個功能、或一小部分使用者打開 compiler,以實際數據驗證「效能真的有改善」,再說服團隊全開
-
-
漸進式導入 React Compiler
-
導入前的確認準備
-
React 版本
-
支援:React 17 / 18 / 19
-
React 19 則不需要額外設定
-
-
專案的 build pipeline 有 Babel
-
用支援 Babel 的工具(含 Vite 的
@vitejs/plugin-react)才掛得上babel-plugin-react-compiler
-
-
裝好 ESLint 規則
-
裝最新版
eslint-plugin-react-hooks,使用官方recommended-latestpreset,裡面已經包含 compiler 相關規則,可以提早看到「哪裡會讓 compiler 放棄優化」
-
-
策略一:用 Babel 的 overrides 做「資料夾級別」導入
- 官方推薦、最適合中大型專案的作法
-
Babel 的
overrides可以讓你對不同路徑的檔案套不同的 plugins- 先挑幾個資料夾開啟 React Compiler,其它地方先不要動
- 以右邊的設定為例,只有在
src/modern/底下的檔案才會被 React compiler 處理,其它檔案不受影響
-
擴大覆蓋率
- 等你對 compiler 有信心,可以逐步加更多路徑,把更多 feature 資料夾加進同一個 override
-
這讓你可以有策略地:
-
全新寫的功能程式碼優先套用
-
穩定、測試充足的模組次之
-
最後才處理 legacy 區(需要先修一堆 Rules of React 的違規)
-

策略二:透過 "use memo" 手動標記要套用的檔案
-
這是「最高控管」模式:你明確標記要編譯的 components / hooks,其它地方則一律不處理
- 在 babel plugin 設定中,將
compilationMode設置為annotation -
用
"use memo"一個一個標記哪些 component / hook 要被 React compiler 處理
- 在 babel plugin 設定中,將
-
在 annotation 模式下,你要記得:
-
想被優化的 component → 一個一個加
"use memo" -
custom hook 也要加
-
新寫的 component / hook 也要記得加(不然就變成沒經過 React compiler 處理)
-
-
什麼情境適合用 Annotation 模式?
-
你要在很敏感的專案上試 compiler(例如金流、權限控管頁面),不想一口氣影響整個專案或資料夾
-
你只想先測試幾個「明顯需要效能優化」 component(例如大型列表、儀表板),驗證效果
-


策略三:Runtime feature flag with gating
-
用 gating 把「要不要用編譯後版本」交給 runtime 的 feature flag 決定
- 此模式下 compiler 會為所有需要編譯的 component / hooks 都產生兩個版本(經過 & 未經過 React compiler 處理)的程式碼,並在 runtime 呼叫時透過 if else 條件式判斷要執行哪個版本
- 這個做法會讓產生出來的程式碼增加,因此有著 bundle size 顯著增加的副作用。React compiler 作用的範圍開越大,重複的程式碼就越多
- 建議只對「少數幾個關鍵功能」做 gating(典型 A/B 用法), 增加的體積就相對可控
-
什麼時候值得用 gating?
- gating 比較適合作為「過渡期 / 實驗用的工具」,而不是常駐的設定
- 想拿同一段程式的「有 compiler / 沒 compiler」實測數據,例如當大型團隊需要數據來說服 stakeholder(PM、SRE)同意全面導入。
-
線上服務不能一次全改,希望先在少數使用者、或特定 region 部分上線 production


Trobleshooting
-
遇到問題時,可以照官方 checklist:
-
先用
"use no memo"把疑似有問題的 component 暫時跳出 compiler-
適合的用途:
-
debug compiler 問題
-
第三方 library 整合時暫時避開有問題的 components / hooks
-
-
是「暫時性逃生門」,而不建議當成「常駐黑名單」的手段
-
-
看 Debugging guide
-
分辨是 compiler 錯、還是原本程式就有 bug 只是被放大
-
-
修 ESLint 報的 Rules of React 違規
-
很多「compiler 編譯失敗」其實是因為程式碼本來就違反了 react 要求的 hooks 規則
-
-
若整個區域不夠穩定 → 考慮暫時改用
compilationMode: 'annotation',只對少數有把握的 components / hooks 開 compiler
-

總結:實務上的導入計劃順序
1. 先裝好 Babel plugin + ESLint
-
babel-plugin-react-compiler -
eslint-plugin-react-hooks最新版(含 compiler 規則)
4. 擴大覆蓋範圍
- 逐步把更多 feature 的資料夾加進 compiler 覆蓋範圍中
- 對 legacy 區域保守,必要時維持不同 overrides
2. 選一個「資料夾」當試驗場
-
用 Babel
overrides指定例如src/modern/** -
跑測試、開 DevTools 看 re-render 行為、量 RUM / 效能指標
3. 整理那一區的 Rules of React 問題
-
把 lint 抓到的不純 render / 亂用 hooks 改掉
-
遇到難纏的就先加
"use no memo",稍後再回頭重構
5. 需要時導入 gating 做 A / B testing
-
對部分流量或特定環境開 compiler,觀察線上數據
-
問題太多就先關 featute flag,程式仍然可以跑未編譯版本
6. 長期:慢慢減少手寫 memoization
-
當你對 compiler 穩定度有信心,可以開始移除多餘的
useMemo/useCallback/React.memo, -
讓「效能優化」更多交給 compiler 管,程式本身回到專注於可讀性與 domain
參考資料
Thanks!
React Compiler
By tz5514
React Compiler
- 253