React 的並發能力不再隻是框架內部的實現細節,而是複雜互動體驗的核心工具。搜尋、篩選、拖曳、富表單和數據面板都可能在同一時間觸發大量更新,團隊需要明確哪些更新必須立即完成,哪些更新可以延後。2026 年,並發模式已經從「可選的實驗特性」變成了 React 應用的默認行為。
渲染優先級要服務用戶意圖
並發 UI 的關鍵不是讓所有渲染都更快,而是讓用戶感知到重要操作更穩定。React 的排程模型將更新按優先級分為幾類:
緊急更新(Urgent Updates) 需要立即響應用戶操作的更新。典型場景包括:
- 輸入框的 typing 反饋
- 按鈕的 click 狀態變化
- 焦點切換和鍵盤導航
- 拖曳操作的即時位置更新
這些更新應該使用默認的同步渲染,不要包在 useTransition 或 useDeferredValue 裡。
過渡更新(Transition Updates) 可以延遲但需要保持一致的更新。React 18 引入的 useTransition 和 startTransition 是這類更新的核心工具:
- 搜尋結果列表的渲染
- 篩選條件變化後的列表重排
- Tab 切換後的內容區更新
- 圖表和圖表的重新計算
把重渲染標記為 transition 後,React 會在緊急更新處理完之後再執行它們,並且如果期間有新的用戶操作,會自動中斷當前的 transition。
延遲更新(Deferred Updates) 可以進一步延遲、甚至在下一幀才需要的更新。useDeferredValue 適用於:
- 大量數據的列表渲染(虛擬列表之外的場景)
- 非關鍵的數據面板刷新
- 後臺統計數據的更新
一個實用的規則:如果你不確定一個更新是否應該延遲,先把它包在 startTransition 裡,然後在 React DevTools Profiler 中觀察它是否阻塞了用戶互動。
Suspense 邊界的藝術
Suspense 可以讓載入狀態更可控,但邊界的選擇直接影響用戶體驗。
邊界不能太碎: 如果每個小組件都是一個獨立的 Suspense 邊界,頁面會出現大量局部載入動畫(spinner),用戶在等待時看到的是閃爍的碎片而不是一個整體。這比一次性載入完整體頁面更讓人不適。
邊界不能太粗糙: 如果整個頁面隻有一個 Suspense 邊界,那慢的組件會阻塞快的組件,一個數據慢會導致整個頁面都在 loading。
推薦的邊界策略:按用戶任務劃分
- 頁面骨架層:最外層 Suspense,包裹整個路由頁面,用於整體載入。fallback 顯示頁面骨架屏。
- 功能區域層:為獨立的用戶任務區域設定 Suspense。比如一個 Dashboard 頁面上,「篩選面板」、「數據表格」、「圖表區」、「推薦側欄」各自是獨立的 Suspense 邊界——用戶可以先生成篩選條件,再等數據載入。
- 組件級:隻在確有必要時才使用。一個參考標準:當某個組件的載入時間超過 2 秒且用戶可能在它載入期間操作其他區域時,才值得為它單獨設定 Suspense 邊界。
一個容易忽視的細節:Suspense 的 fallback 設計。好的 fallback 應該是當前區域的結構佔位,而不是一個轉圈的 spinner。用骨架屏代替 spinner,可以大幅減少用戶的等待焦慮。
useTransition 和 useDeferredValue 的選擇
這兩個 Hook 看起來相似,但適用場景不同:
使用 useTransition 當更新由用戶操作觸發:
function SearchPage() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const handleInput = (e) => {
// 高優先級:立即更新輸入框的值
setQuery(e.target.value);
// 低優先級:搜尋結果可以等
startTransition(() => {
setSearchResults(searchData(e.target.value));
});
};
return (
<>
<input value={query} onChange={handleInput} />
{isPending && <SmallSpinner />}
<SearchResults results={searchResults} />
</>
);
}
使用 useDeferredValue 當更新來自外部數據來源:
function Dashboard({ serverData }) {
// 當 serverData 變化時,可以延遲舊頁面的渲染
const deferredData = useDeferredValue(serverData);
return (
<div style={{ opacity: serverData !== deferredData ? 0.5 : 1 }}>
<HeavyChart data={deferredData} />
</div>
);
}
關鍵區別:useTransition 給你 isPending 標誌(知道甚麼時候在等待),useDeferredValue 給你新值和舊值的對比(可以同時展示舊數據並把新數據標記為 stale)。
並發模式下的效能診斷
React 2026 的效能診斷已經從「看渲染次數」升級到了「看互動鏈路」。三個工具各司其職:
React DevTools Profiler:
- 看組件為甚麼渲染(props 變化、state 變化、context 變化、hooks 變化)
- 看每個 commit 的耗時
- 識別不必要的重新渲染
Chrome Performance 面板:
- 看主線程的長任務分佈
- 看 React 排程器的工作模式(是否正確地使用時間切片)
- 看互動事件的響應延遲(從用戶點擊到瀏覽器處理)
RUM 數據(Web Vitals):
- INP(Interaction to Next Paint):2026 年最重要的互動指標,替代了 FID
- 按頁面、裝置和地區分組的 P75 和 P95 數據
- 發佈前後的對比數據
這三類數據要放在一起看:Profiler 告訴你「這個組件渲染了 5 次」,Performance 面板告訴你「這 5 次渲染佔用了 200ms 主線程」,RUM 告訴你「這個頁面的 P95 INP 是 180ms」。三者結合,才能判斷是否值得優化。
常見反模式
反模式 1:把一切包在 transition 裡 不是所有非緊急更新都需要 transition。如果一個更新的計算量很小(例如切換一個布爾值),把它放進 transition 反而增加了複雜度。
反模式 2:在 transition 裡做副作用startTransition 隻應該包含狀態更新。如果在裡面發了網絡請求、寫了 localStorage、觸發了埋點,React 中斷 transition 時狀態回滾了但副作用已經執行了——這會製造詭異的 bug。
反模式 3:用 useMemo/useCallback 代替並發特性 並發模式解決的是「渲染甚麼時候發生」的問題,memoization 解決的是「渲染甚麼內容」的問題。兩者互補,不能互相替代。
小結
React 2026 的並發實踐,本質是在複雜界面中重新分配計算資源。把高優先級互動(用戶正在操作的內容)保護好,把低優先級渲染(用戶可以等待的內容)安排在瀏覽器空閒時間執行。關鍵在於理解三個工具的分工:useTransition 用於用戶觸發的延遲更新,useDeferredValue 用於外部數據驅動的延遲渲染,Suspense 用於按用戶任務劃分載入邊界。做好這三件事,就能讓複雜的 React 應用在互動體驗上脫胎換骨。
