React 19.3 有哪些值得升級?ViewTransition、Fragment Refs 與 browser() 實作邊界
React 19.3 已在 2026 年 9 月 9 日發布到 npm。這次升級最值得注意的不是「又多幾個 API」,而是三個常被混在一起的問題終於有比較清楚的邊界:View Transition 負責把 transition update 變成動畫、Fragment Refs 負責在不增加 wrapper 的情況下管理一組子節點,而 browser() 負責把無法產生有意義 SSR HTML 的元件交給 client。
直接答案是:如果你需要跨頁或列表的進出場動畫,先評估 <ViewTransition>;如果元件回傳 Fragment 卻仍需要 focus、observer 或 event listener,再評估 Fragment Refs;只有真的無法在 server 產生穩定初始 HTML 時,才使用 use(browser())。三者不是同一種「讓 React 更快」的開關。
React 19.3 的四個重點,先分清用途
官方這次把穩定功能和 React DOM/Server Components 的更新放在同一篇 release post。實作前可以先用下面的表格縮小範圍:
| 功能 | 解決的問題 | 不應該拿來做什麼 |
|---|---|---|
<ViewTransition> | 在 Transition、Suspense reveal 或 deferred update 中協調 enter/exit/update/share 動畫 | 不要包住每個小元件,或把 urgent update 硬改成動畫 |
| Fragment Refs | 對沒有單一 DOM parent 的 Fragment 子節點做 focus、事件或可見度觀察 | 不要把它當成任意 DOM reparent 或繞過元件封裝的通用 ref |
use(browser()) | 元件只在瀏覽器有意義,需要 opt out server rendering | 不要用來掩蓋可修正的 hydration mismatch |
| Trusted Types support | 讓 React 傳遞 Trusted Types 物件時不被先轉成字串 | 不會自動建立 sanitizer,也不會代替 CSP policy |
這些 API 在 React 19.3 穩定,不代表所有 framework adapter、SSR pipeline 或瀏覽器都已經以相同方式整合。升級時仍要把 React、React DOM、framework、測試 renderer 和 TypeScript 型別一起檢查。
<ViewTransition>:讓有意義的 state transition 產生動畫
最小用法是把需要協調的畫面包在 <ViewTransition> 裡,並且由 startTransition 標記這次更新:
import { ViewTransition, startTransition, useState } from 'react'
export function DetailsToggle() { const [showDetails, setShowDetails] = useState(false)
return ( <> <button onClick={() => { startTransition(() => setShowDetails((value) => !value)) }} > {showDetails ? '收合' : '展開'} </button>
{showDetails && ( <ViewTransition> <Details /> </ViewTransition> )} </> )}React 會依 tree 的變化判斷 enter、exit、update 或 share。預設可以先得到 cross-fade,再用 CSS 的 View Transition class 或 onEnter、onExit、onShare、onUpdate 事件細調。要注意的是,沒有被標記為 Transition 的 urgent update 不會因此自動變成動畫;這是保留即時回應的設計。
動畫測試的三個檢查點
- Loading fallback 是否被拖慢。 Suspense fallback 通常應立即出現,已快取的內容也不必每次 reveal 都重新淡入。
- Reduced motion 是否有替代樣式。
prefers-reduced-motion: reduce應停用或縮短非必要動畫。 - 狀態改變是否真的屬於 transition。 搜尋、按鈕回饋或表單錯誤等 urgent update 若被包進 transition,使用者可能感覺到延遲。
如果頁面本來就有原生 DOM 的 view-transition-name、複雜的 portal 或第三方動畫庫,請把同一個互動在支援與不支援的瀏覽器各測一次。<ViewTransition> 目前是 DOM 功能,不能直接假設 React Native 有相同支援。
Fragment Refs:沒有 wrapper 也能管理一組子節點
元件回傳多個 sibling 時,傳統做法往往是加一個 <div> 當 ref anchor;這可能改變 CSS layout、accessibility tree 或 flex/grid 結構。React 19.3 的 Fragment Refs 讓 ref 可以直接放在 <Fragment> 上:
import { Fragment, useEffect, useRef } from 'react'
export function HeadingGroup({ headings }: { headings: string[] }) { const groupRef = useRef(null)
useEffect(() => { groupRef.current?.focus() }, [])
return ( <Fragment ref={groupRef}> {headings.map((heading) => ( <h2 tabIndex={-1} key={heading}> {heading} </h2> ))} </Fragment> )}這類 ref 提供的是一組子節點的有限操作介面,包括 event listener、focus/blur、IntersectionObserver/ResizeObserver 以及量測和 scroll。它不是一個虛擬 DOM 節點,也不是把 children 變成可以任意搬動的容器;程式仍要尊重 React 的 reconciliation 和元件生命週期。
實作時特別注意:
- 要把
ref放在真正的Fragment,不是任意 shorthand<>...</>。 - 先確認你需要的是「群組操作」,不是某一個深層 DOM 元素的 ref。
- observer 需要在 effect 中註冊和清理,避免每次 render 重複連線。
- 若只為了 focus 一個按鈕,直接讓該按鈕接收 ref 通常更簡單。
如果你的元件是第三方 library,Fragment Refs 可以減少為了暴露單一 wrapper 而修改 API 的需求;但這也表示元件的 DOM 子節點成為行為依賴,升級 library 時要補上 structure-level regression test。
use(browser()):只對真正 browser-only 的元件使用
Server rendering 的元件應該盡量在 server 和 client 產生相同的初始 HTML。遇到 localStorage、裝置時區、瀏覽器權限或只能在 window 存在時取得的資料,React 19.3 提供 browser() 讓元件主動退出 SSR:
import { Suspense, use } from 'react'import { browser } from 'react-dom'
function LocalTimeZone() { use(browser())
const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone return <p>{timeZone}</p>}
export function TimeZonePanel() { return ( <Suspense fallback={<p>正在讀取時區……</p>}> <LocalTimeZone /> </Suspense> )}server render 時,最近的 Suspense fallback 會進到 HTML;元件 hydrate 後,use(browser()) 不會在 client suspend,元件才繼續讀取 browser-only API。這個模型比在 useEffect 裡設一個 mounted state 更直接,但代價是該區塊的 server HTML 只有 fallback,可能影響 SEO、首屏內容和無 JavaScript 的可用性。
可以用這個決策順序:
- 能不能由 server 傳入固定的
initialValue?可以的話,優先用它,保留穩定初始 HTML。 - 能不能把 browser-only 讀取延後到互動發生時?可以的話,不要讓整個元件退出 SSR。
- 只有在沒有合理 server fallback 時,才使用
use(browser()),並替 Suspense fallback 寫好 loading、accessibility 和 layout 保留。
不要只把 typeof window !== 'undefined' 換成 browser() 就算完成;兩者的目的都是處理環境差異,但 browser() 會和 Suspense、SSR streaming 與 hydration 一起工作,驗收範圍更大。
Trusted Types:React 支援不等於網站已經安全
React 19.3 會在 Trusted Types 物件傳到 DOM injection sink 前保留它,而不是先用字串串接把 TrustedHTML、TrustedScript 或 TrustedScriptURL 轉回普通字串。要發揮效果,網站仍需要瀏覽器 CSP 的 require-trusted-types-for 'script' 和可信任的 sanitizer policy。
升級後如果要啟用,請分三層驗證:
| 層級 | 要確認什麼 |
|---|---|
| React 元件 | dangerouslySetInnerHTML、script URL 和第三方 widget 使用的值來源 |
| sanitizer policy | 只有經過允許的清理流程才建立 Trusted Types 物件 |
| CSP/部署 | report-only 觀察期、正式 enforcement、legacy browser fallback |
不要因為 React release note 寫了 Trusted Types support,就把現有的 HTML sanitize 移除。React 只改善值在 DOM API 間的傳遞方式,內容安全仍由你的 policy、來源驗證和 CSP 負責。
升級驗收:一次只導入一種新能力
React 19.3 升級建議分階段做:
pnpm why react react-dompnpm exec tsc --noEmitpnpm testpnpm build先只升級 react、react-dom 和對應型別,確認 hydration、Strict Mode、Suspense、router 與 test renderer 沒有回歸;再以小型元件導入 <ViewTransition>;最後才在確定 server fallback 可接受的區塊使用 browser()。若你原本就直接使用 DOM state-preserving API,也可以參考 moveBefore() 的狀態邊界;它和 React reconciliation 一樣,都要求先確認「誰擁有這個 DOM」。
CI 至少加上以下情境:
- SSR 輸出的 HTML 與 client 第一次 render 是否符合預期。
- transition 中的 fallback、錯誤邊界和取消更新。
- Fragment Ref 的 focus、observer cleanup 與無 wrapper layout。
prefers-reduced-motion和不支援 View Transition 的瀏覽器。- Trusted Types report-only 與正式 CSP 的 console/response header。
結論:穩定 API 不代表可以跳過邊界設計
React 19.3 的價值,是把常見的 UI 需求放進更清楚的 primitives:動畫交給 <ViewTransition>,群組 DOM 行為交給 Fragment Refs,browser-only rendering 交給 use(browser()),Trusted Types 則改善安全 API 的相容性。升級時先選一個真實問題導入,為它補上 SSR、fallback、accessibility 和 reduced-motion 測試,再擴大範圍,比一次把所有新 API 套進元件庫更容易定位回歸。
常見問題
Q: <ViewTransition> 包住元件後,任何 state 更新都會動畫嗎?
A: 不會。React 主要對 Transition、Suspense reveal 或 deferred value 造成的更新協調 View Transition;urgent update 不會因為被包住就自動變成動畫。動畫應該服務狀態變化的語意,不是把所有 render 都變慢。
Q: Fragment Refs 可以取代所有 DOM ref 嗎?
A: 不行。它適合一組 Fragment 子節點的 focus、事件、觀察和量測;若你只需要操作一個具名元素,直接把一般 ref 傳給該元素更清楚。Fragment Refs 也不代表可以繞過 React 隨意搬動或重排 DOM。
Q: 使用 use(browser()) 會不會讓 SEO 消失?
A: 這個元件在 server 會以 Suspense fallback 呈現,因此原本由元件輸出的內容可能不會出現在初始 HTML。需要被搜尋引擎理解的標題、描述和主要內容,應優先由 server 產出;browser-only 元件請提供有意義的 fallback 或 server initialValue。
參考資料:
回報錯字、失效連結,或告訴我你想看的延伸主題。