React Fiber 架構(gòu)解決頁面卡頓問題的全過程
React Fiber 架構(gòu)詳解:為什么它能解決頁面卡頓問題?

本文從問題與目標(biāo)、核心數(shù)據(jù)結(jié)構(gòu)、調(diào)度與中斷、渲染階段與提交階段、優(yōu)先級與 lanes、并發(fā)特性到常見誤區(qū)與優(yōu)化建議,全景式拆解 React Fiber,為何它能夠顯著降低交互卡頓并提升可響應(yīng)性。
TL;DR
- Fiber 將渲染過程切成可中斷的小任務(wù)并在合適的時機(jī)繼續(xù)執(zhí)行
- 通過優(yōu)先級與 lanes 模型,優(yōu)先處理緊急交互,延遲非關(guān)鍵更新
- 渲染分兩階段:可打斷的 render 與一次性提交的 commit,DOM 變更集中且短促
- 調(diào)度器以時間片與讓渡機(jī)制避免長任務(wù)阻塞主線程,從而減少卡頓
- 并發(fā)特性如
useTransition、Suspense、選擇性水合都建立在 Fiber 能力之上
卡頓的來源與架構(gòu)目標(biāo)
- 來源:長耗時任務(wù)阻塞事件處理與動畫幀,渲染與計算無法及時讓出主線程
- 目標(biāo):將更新拆解為細(xì)粒度單元,基于優(yōu)先級可中斷、可恢復(fù)且可重試的執(zhí)行模型
- 兼容:保留類組件與函數(shù)組件的語義,支持 SSR、Hydration 與后續(xù)擴(kuò)展
Fiber 的核心數(shù)據(jù)結(jié)構(gòu)
每個 Fiber 對應(yīng)一次渲染過程中的“工作單元”,以鏈表樹結(jié)構(gòu)組織:
type Lane = number
interface Fiber {
tag: number
key: null | string
type: any
stateNode: any
return: Fiber | null
child: Fiber | null
sibling: Fiber | null
alternate: Fiber | null
lanes: Lane
flags: number
memoizedProps: any
memoizedState: any
updateQueue: any
}child與sibling:鏈?zhǔn)綐浔闅v更適合增量執(zhí)行alternate:當(dāng)前樹與待提交的工作樹雙緩沖切換lanes與flags:控制優(yōu)先級與記錄副作用
兩階段模型:render 與 commit
- render 階段:構(gòu)建工作樹、計算變更列表,允許中斷與恢復(fù)
- commit 階段:一次性將變更應(yīng)用到 DOM 與副作用,盡量短小
- 好處:長計算不阻塞瀏覽器事件與動畫,提交階段集中且可控
調(diào)度與時間片:避免長任務(wù)阻塞
- 以切片執(zhí)行的工作循環(huán)進(jìn)行遍歷,接近以下偽代碼:
let workInProgress: Fiber | null = null
function shouldYield() {
return performance.now() >= deadline
}
function workLoop() {
while (workInProgress && !shouldYield()) {
workInProgress = performUnitOfWork(workInProgress)
}
if (workInProgress) scheduleNextTick(workLoop)
}- 通過
shouldYield在每個單元之間檢查是否需要讓渡控制權(quán) - 調(diào)度器依據(jù)任務(wù)的 lanes 與瀏覽器空閑時間安排后續(xù)執(zhí)行
優(yōu)先級與 lanes:誰更重要先做誰
- lanes 是位掩碼表示的多優(yōu)先級融合模型
- 高優(yōu)先級任務(wù)包含輸入響應(yīng)與選擇性水合,低優(yōu)先級用于非關(guān)鍵渲染
- 多任務(wù)可歸并到 lanes,調(diào)度器據(jù)此選擇下一個單元
為什么能緩解卡頓
- 將不可打斷的同步樹遍歷改造為可暫停的單元工作
- 遇到輸入事件或動畫幀時及時讓渡,保障主線程響應(yīng)
- 區(qū)分緊急與非緊急更新,減少無關(guān)變更搶占時間片
- commit 階段將 DOM 變更批量一次性應(yīng)用,縮短布局與繪制沖擊
并發(fā)特性與 Fiber 的關(guān)系
useTransition與startTransition:將更新標(biāo)記為非緊急,排在緊急交互之后Suspense:在資源未就緒時掛起某些子樹,避免阻塞頁面可交互區(qū)域- 選擇性水合:SSR 下優(yōu)先水合用戶交互路徑上的組件
useDeferredValue:推遲昂貴的派生計算以保證輸入響應(yīng)速度
工作循環(huán)與單元執(zhí)行
function performUnitOfWork(fiber: Fiber): Fiber | null {
beginWork(fiber)
if (fiber.child) return fiber.child
let node: Fiber | null = fiber
while (node) {
completeWork(node)
if (node.sibling) return node.sibling
node = node.return
}
return null
}
beginWork生成子節(jié)點(diǎn)與比較 propscompleteWork構(gòu)建副作用列表與準(zhǔn)備提交信息- 在每個單元之間檢查讓渡條件,實(shí)現(xiàn)可中斷的深度優(yōu)先遍歷
典型場景與實(shí)踐
- 大列表渲染:結(jié)合虛擬滾動與并發(fā)更新,輸入滾動時保持流暢
- 復(fù)雜表單輸入:將非關(guān)鍵重排與昂貴計算置于過渡更新
- 數(shù)據(jù)獲取與占位:用
Suspense顯示骨架屏并保持交互域可用 - SSR 水合:優(yōu)先水合導(dǎo)航與交互區(qū)域,其余延后進(jìn)行
常見誤區(qū)
- 認(rèn)為 Fiber 自動優(yōu)化所有卡頓。若 render 執(zhí)行中包含長耗時同步邏輯,仍會占用時間片
- 在 commit 階段做重計算或強(qiáng)制同步布局會導(dǎo)致幀率下降
- 不當(dāng)?shù)念l繁狀態(tài)變更會增加任務(wù)競爭,需進(jìn)行歸并與降頻
- 忽視 keys 與結(jié)構(gòu)穩(wěn)定性會增加無效重建
性能建議
- 用
useTransition或批處理分離緊急與非緊急更新 - 將昂貴計算移動到 memo 化或后臺任務(wù),減少 render 成本
- 控制 commit 的 DOM 變更數(shù)量與粒度,避免反復(fù)測量布局
- 優(yōu)化列表與表格,采用虛擬化與分塊渲染
- 使用 Profiler 與 Performance 工具定位長任務(wù)與熱點(diǎn)組件
總結(jié)
Fiber 的核心價值是以可中斷、可恢復(fù)的增量執(zhí)行模型替代不可分割的同步渲染。它結(jié)合優(yōu)先級與時間片調(diào)度,將緊急交互放在首位,并通過分階段提交降低 DOM 變更的集中沖擊。理解 Fiber 的數(shù)據(jù)結(jié)構(gòu)、工作循環(huán)與并發(fā)特性,有助于在真實(shí)項(xiàng)目中系統(tǒng)性地治理卡頓問題并提升整體可響應(yīng)性。
進(jìn)階解析:優(yōu)先級體系與 lanes 細(xì)節(jié)
- React 18 將早期的 Scheduler 優(yōu)先級(Immediate/UserBlocking/Normal/Low/Idle)抽象為 lanes;一個更新可以占用多個 lanes
- lanes 使用位掩碼,便于合并與比較;渲染時選擇最高優(yōu)先級的 lane 作為下一幀的工作目標(biāo)
- 離散事件(點(diǎn)擊、輸入)通常賦予高優(yōu)先級;連續(xù)事件(滾動、鼠標(biāo)移動)優(yōu)先級較低并可被打斷
- 協(xié)調(diào)策略:
- 合并同類型低優(yōu)更新,減少重復(fù)工作
- 當(dāng)高優(yōu)更新到來時,打斷當(dāng)前 render,保存現(xiàn)場并優(yōu)先處理高優(yōu)
- 超時控制避免低優(yōu)任務(wù)長期饑餓
雙緩沖與副作用標(biāo)記
- 雙緩沖:
current指向已提交的樹;workInProgress是正在構(gòu)建的工作樹,二者通過alternate互為鏡像 - 副作用標(biāo)記(
flags)記錄節(jié)點(diǎn)的變更類型(Placement/Update/Deletion 等) - render 階段構(gòu)建副作用鏈表,commit 階段按序遍歷執(zhí)行,確保 DOM 變更集中完成
被打斷的渲染如何恢復(fù)
- 渲染在單元邊界處可暫停,恢復(fù)時從上次的
workInProgress繼續(xù) - bailout:若某子樹 props/state 未變化或
shouldComponentUpdate/React.memo判定無需更新,則跳過子樹計算 - 重試與掛起:配合
Suspense對未就緒數(shù)據(jù)的子樹進(jìn)行掛起,數(shù)據(jù)可用后再重試該子樹
并發(fā)渲染與用戶體驗(yàn)
- 并發(fā)渲染不是并行執(zhí)行,而是允許在同一線程的不同時間片中交替推進(jìn)多個樹的渲染
- 用戶可見區(qū)域優(yōu)先:借助優(yōu)先級與選擇性水合,優(yōu)先渲染交互路徑上的組件,降低首屏交互延遲
- 資源阻塞治理:
Suspense將依賴資源的子樹掛起,用占位/骨架提升感知速度,避免整頁卡住
調(diào)度器內(nèi)部要點(diǎn)
- 時間片長度根據(jù)環(huán)境動態(tài)調(diào)整,瀏覽器與 Node 環(huán)境采用不同的定時/消息通道策略
- 在支持的瀏覽器中可利用
navigator.scheduling.isInputPending()判斷是否存在待處理的輸入事件,從而更積極地讓渡 - 微任務(wù)與宏任務(wù)配合,確保任務(wù)隊(duì)列與渲染隊(duì)列之間的公平性,避免某一方長時間獨(dú)占
代碼示例:用過渡更新緩解輸入卡頓
import { useMemo, useTransition, useState } from 'react'
export default function FilterList({ items }: { items: string[] }) {
const [query, setQuery] = useState('')
const [isPending, startTransition] = useTransition()
const filtered = useMemo(() => {
const q = query.toLowerCase()
return items.filter(i => i.toLowerCase().includes(q))
}, [items, query])
return (
<div>
<input
value={query}
onChange={e => {
const v = e.target.value
startTransition(() => setQuery(v))
}}
placeholder="輸入過濾關(guān)鍵詞"
/>
{isPending && <span>計算中…</span>}
<ul>
{filtered.map(i => <li key={i}>{i}</li>)}
</ul>
</div>
)
}- 將
setQuery放入startTransition,把過濾計算標(biāo)記為非緊急更新,輸入響應(yīng)優(yōu)先不卡頓
代碼示例:Suspense 與數(shù)據(jù)就緒的掛起
function UserCard() {
const user = useUserResource() // 內(nèi)部拋出 Promise,待數(shù)據(jù)就緒再繼續(xù)渲染
return <div>{user.name}</div>
}
export default function Page() {
return (
<Suspense fallback={<Skeleton />}>
<UserCard />
</Suspense>
)
}- 未就緒時渲染
fallback,就緒后恢復(fù)渲染子樹;配合 Fiber 的可中斷模型提升感知速度
SSR 與選擇性水合
- React 18 的流式 SSR 將 HTML 分塊輸出,客戶端根據(jù)用戶交互路徑優(yōu)先水合必要組件
- 選擇性水合避免一次性水合整棵樹導(dǎo)致的主線程擁塞,優(yōu)先保證可點(diǎn)擊/可輸入?yún)^(qū)域
診斷與優(yōu)化清單
- 渲染開銷:
- 使用 Profiler 捕獲長耗時組件
- 用
memo/useMemo/useCallback控制派生與重建
- 提交階段:
- 合并 DOM 變更,避免在 commit 中進(jìn)行昂貴計算或強(qiáng)制布局
- 減少同步測量與多次讀寫交錯導(dǎo)致的布局抖動
- 任務(wù)競爭:
- 將非關(guān)鍵更新放入
useTransition或批處理 - 對頻繁狀態(tài)用節(jié)流/防抖或批量歸并
- 將非關(guān)鍵更新放入
- 結(jié)構(gòu)穩(wěn)定性:
- 正確使用
key,減少無謂 diff 與重排 - 列表/表格采用虛擬化與分塊渲染
- 正確使用
常見反模式與替代方案
- 在渲染或副作用中執(zhí)行大循環(huán)或密集計算,可改為后臺 Worker 或增量計算
- 在
useEffect中頻繁、同步地讀取布局并寫入樣式,改為批量讀后批量寫或轉(zhuǎn)移到動畫幀 - 不區(qū)分緊急/非緊急更新導(dǎo)致輸入抖動,使用
startTransition分離
小結(jié)與實(shí)踐建議
- 評估場景:是否存在長列表、復(fù)雜派生、頻繁交互或數(shù)據(jù)阻塞
- 策略組合:優(yōu)先級劃分 + 并發(fā)渲染 + 占位與水合 + 結(jié)構(gòu)優(yōu)化
- 工具鏈:Profiler、Performance、Lighthouse 聯(lián)合定位瓶頸,結(jié)合日志采樣觀察真實(shí)終端表現(xiàn)
到此這篇關(guān)于React Fiber 架構(gòu)解決頁面卡頓問題的全過程的文章就介紹到這了,更多相關(guān)React Fiber 架構(gòu)頁面卡頓內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React利用lazy+Suspense實(shí)現(xiàn)路由懶加載
這篇文章主要為大家詳細(xì)介紹了React如何利用lazy+Suspense實(shí)現(xiàn)路由懶加載,文中的示例代碼簡潔易懂,感興趣的小伙伴可以跟隨小編一起了解一下2023-06-06
React?Hooks--useEffect代替常用生命周期函數(shù)方式
這篇文章主要介紹了React?Hooks--useEffect代替常用生命周期函數(shù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
React 中常用的幾種路由跳轉(zhuǎn)方式小結(jié)
基本路由跳轉(zhuǎn)是最常見的一種方式,下面介紹React 中常用的幾種路由跳轉(zhuǎn)方式,感興趣的朋友一起看看吧2023-12-12
React Native 集成jpush-react-native的示例代碼
這篇文章主要介紹了React Native 集成jpush-react-native的示例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08
React項(xiàng)目動態(tài)設(shè)置title標(biāo)題的方法示例
這篇文章主要介紹了React項(xiàng)目動態(tài)設(shè)置title標(biāo)題的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09
Electron打包React生成桌面應(yīng)用方法詳解
這篇文章主要介紹了React+Electron快速創(chuàng)建并打包成桌面應(yīng)用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-12-12
React.memo?React.useMemo對項(xiàng)目性能優(yōu)化使用詳解
這篇文章主要為大家介紹了React.memo?React.useMemo對項(xiàng)目性能優(yōu)化的使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01

