React Fiber 結(jié)構(gòu)
介紹
React Fiber 是對React核心算法的重新實(shí)現(xiàn),也是React團(tuán)隊(duì)花了兩年時(shí)間研究的結(jié)晶揖闸。
React Fiber 的目標(biāo)是提升其對于動畫秉剑、布局替劈、手勢等場景的適用性慨畸。它的核心功能就是增量渲染:一種將渲染工作分解為多個(gè)區(qū)塊并將其分散到每一幀里面伞梯。
其他核心功能還包括隨著程序中新的update引起的暫定写隶、終止和繼續(xù)等倔撞;以及為不同任務(wù)分配優(yōu)先級;和最新的并發(fā)性慕趴。
關(guān)于文檔
Fiber引入了一些比較新的概念痪蝇,所以僅通過代碼很難理解鄙陡。這片文檔最開始是我在React項(xiàng)目中實(shí)現(xiàn)Fiber的筆記的集合。而隨著它的發(fā)展躏啰,我也逐漸意識到這些筆記對其他人也可能是比較有用的資源趁矾。
我盡量通過最簡單的語言,并明確定義關(guān)鍵術(shù)語來避免太專業(yè)化解釋给僵。在需要的時(shí)候還會有大量的外部鏈接來解釋毫捣。
請注意我并不是React團(tuán)隊(duì)中的一員,所以這篇文章也并非官方權(quán)威文檔帝际,但是我也找了React團(tuán)隊(duì)的的成員對其準(zhǔn)確性做了審查蔓同。
這也還是一個(gè)正在進(jìn)行的工作。Fiber還在持續(xù)演變蹲诀,在完成之前都有可能進(jìn)行很大的改動牌柄。設(shè)計(jì)文檔也會同步在這里更新,也歡迎大家來指正侧甫。
我的目標(biāo)是你讀完本篇文章之后對Fiber有足夠的了解,以便跟上后續(xù)的演變蹋宦,甚至為React作出貢獻(xiàn)披粟。
先決條件
強(qiáng)烈建議在讀本文之前理解一下概念:
- React 組件 - "Component" 是非常重要的術(shù)語,需要牢牢掌握冷冗。
- Reconciliation - 對React reconciliation算法的高級描述守屉。
- React Basic Theoretical Concepts - 對沒有實(shí)現(xiàn)負(fù)擔(dān)的的React的概念描述. 剛開始的時(shí)候可能很多東西沒什么意義,但是隨著閱讀深入會更有意義蒿辙。
- React Design Principles -需要特別關(guān)注scheduling部分拇泛。他很好的解釋了為什么會有React Fiber。
review
請?jiān)诖舜_認(rèn)理解了之前的先覺條件思灌。
在深入文檔之前俺叭,先了解幾個(gè)重要的概念。
什么是reconciliation泰偿?
reconciliation
React用來對比兩顆虛擬DOM樹的算法熄守,以判斷哪些DOM需要更新。
update
用于呈現(xiàn)React程序中的更改耗跛,一般是setState引發(fā)的裕照,最終重新渲染DOM。
React的核心思想是考慮全局更新调塌,這讓開發(fā)人員可以只考慮申明式來編寫代碼晋南,而不需要考慮中間過程。例如(A到B羔砾,B到C负间,C到A等等)偶妖。
事實(shí)上,有一點(diǎn)改動就更新整個(gè)app只存在于很少一部分app唉擂,而在真實(shí)的場景餐屎,這是很浪費(fèi)性能的。React對此做了優(yōu)化玩祟,可以保證性能的同時(shí)做到完整更新腹缩,這就是reconciliation 的部分功能。
reconciliation是虛擬DOM背后的核心算法空扎,高級一點(diǎn)的描述是:渲染React程序的時(shí)候藏鹊,將相應(yīng)的節(jié)點(diǎn)樹保存在內(nèi)存中。然后將該節(jié)點(diǎn)樹刷新到渲染環(huán)境中--例如瀏覽器转锈,它會轉(zhuǎn)化成一系列的DOM操作盘寡。當(dāng)程序更新的時(shí)候(通常是setState),會重新生成一顆新的樹撮慨。通過兩個(gè)節(jié)點(diǎn)樹的比較來計(jì)算這些更新需要哪些操作竿痰。
盡管Fiber是對reconciliation的重寫,但是React文檔中的高級算法的描述大致相同砌溺,主要在于:
- 假如不同組件類型生成不同的樹影涉,react不會區(qū)分他們,二是直接替換规伐。
- list主要用key來做區(qū)分蟹倾,這要求key必須“穩(wěn)定、可靠并且唯一”猖闪。
Reconciliation 和render
DOM只是react可以渲染的環(huán)境之一鲜棠,其他的還有通過react-native實(shí)現(xiàn)iOS和Android視圖的渲染(所以說虛擬DOM用詞很不恰當(dāng))。
react之所以能做到這一點(diǎn)是因?yàn)閞eact在設(shè)計(jì)的時(shí)候就已經(jīng)考慮到了將Reconciliation和render分開培慌。reconciler負(fù)責(zé)計(jì)算樹的更新豁陆,而render根據(jù)這些信息來負(fù)責(zé)應(yīng)用程序的更新。
這種分離意味著React DOM 和React Native 可以保證使用React Core提供的相同的reconciler而且使用不同的渲染方法吵护。
Fiber重新實(shí)現(xiàn)了reconciler 献联, 盡管渲染方法需要重新修改以支持(并利用)新的體系結(jié)構(gòu),但它基本上與渲染無關(guān)何址。
Scheduling
Scheduling
確定何時(shí)work的過程
work
必須執(zhí)行的任何計(jì)算里逆,work一般是指更新的結(jié)果(例如setState)。
React的 Design Principles 這篇文檔在這個(gè)主題上非常出色用爪,這里引用一下:
在當(dāng)前的實(shí)現(xiàn)中原押,react遞歸的遍歷樹,并在單個(gè)任務(wù)中調(diào)用整個(gè)更新后的render函數(shù)偎血,但是以后可能會考慮延遲一些更新诸衔,以免丟幀盯漂。
這是React 模式中常見的主題,一些流行的庫實(shí)現(xiàn)了“push”方法笨农,在有新數(shù)據(jù)更新的時(shí)候可用就缆。但是React堅(jiān)持使用“pull”方法,這可以將計(jì)算延遲到需要的時(shí)候谒亦。
React不是一個(gè)常規(guī)的數(shù)據(jù)處理庫竭宰,而是用于構(gòu)建用戶界面的庫。而我們認(rèn)為它在應(yīng)用程序中唯一的作用就是計(jì)算什么相關(guān)份招,什么不相關(guān)切揭。
如果有些東西不在當(dāng)前界面,我們可以延遲跟這個(gè)相關(guān)的邏輯锁摔。如果數(shù)據(jù)量太大廓旬,導(dǎo)致可能丟幀,我們會批量更新谐腰。我們可以將用戶交互(比如按鈕點(diǎn)擊引起的動畫)優(yōu)先于次要的后臺工作(例如網(wǎng)絡(luò)請求加載的新內(nèi)容)孕豹,以避免丟幀。
關(guān)鍵點(diǎn)在于:
- 在用戶界面中十气,不必立即應(yīng)用每個(gè)更新巩步。事實(shí)上,這樣很浪費(fèi)性能桦踊,還會導(dǎo)致幀下降降低用戶體驗(yàn)。
- 不同類型的更新有不同的優(yōu)先級:動畫更新優(yōu)先于數(shù)據(jù)存儲的更新终畅。
- 基于“push”的程序要求程序(你籍胯,就是你)決定如何安排工作,而基于“pull”的模式(react)會更加智能离福,并幫你決定杖狼。
當(dāng)前React并沒有充分利用Scheduling的優(yōu)勢,一次更新會導(dǎo)致立刻重新渲染整個(gè)子樹妖爷。所以Fiber背后的思想就是徹底革新整個(gè)核心算法以充分利用Scheduling的優(yōu)勢蝶涩。
現(xiàn)在我們開始繼續(xù)深入Fiber的實(shí)現(xiàn),后面的內(nèi)容會更加“技術(shù)”絮识,請確保以上的內(nèi)容你已經(jīng)理解绿聘。
什么是Fiber
下面將討論React Fiber體系結(jié)構(gòu)的核心,F(xiàn)iber是比開發(fā)人員想象的低得多的抽象層次舌。如果你發(fā)現(xiàn)你理解不了熄攘,別沮喪,繼續(xù)堅(jiān)持下去彼念,最終肯定能理解挪圾。(等你理解的時(shí)候可以對本篇文章提點(diǎn)建議)
Here we go浅萧。
現(xiàn)在已經(jīng)確定,F(xiàn)iber的主要目標(biāo)是利用React的Scheduling的優(yōu)勢哲思,具體來說需要滿足以下幾點(diǎn):
- 暫停工作洼畅,并回來
- 為不同類型任務(wù)分配優(yōu)先級
- 重用以前完成的工作
- 不需要的時(shí)候終止任務(wù)
為了做到這一點(diǎn),我們需要一種將工作分解成多個(gè)單元的方法棚赔。從某種意義上來說帝簇,這就是Fiber。Fiber是一個(gè)最小工作單元忆嗜。
為了更近一步己儒,我們可以回顧一下 React components as functions of data, 通常表示為:
v = f(d)
因此,呈現(xiàn)整個(gè)react程序類似于調(diào)用一個(gè)函數(shù)捆毫,該函數(shù)的主體有屌用其他函數(shù)闪湾,以此類推。所以在思考Fiber的時(shí)候绩卤,這種類比會很有幫助途样。
計(jì)算機(jī)通常使用調(diào)用堆棧來跟蹤程序執(zhí)行的方式,一個(gè)函數(shù)被調(diào)用的時(shí)候濒憋,一個(gè)新的stack frame被添加到堆棧中何暇,這個(gè)stack frame也代表了這個(gè)函數(shù)的工作也被執(zhí)行了。
在處理UI的時(shí)候凛驮,較大的問題在于如果一次性執(zhí)行太多任務(wù)裆站,會導(dǎo)致動畫掉幀并顯得斷斷續(xù)續(xù)。而且黔夭,如果最新的更新取代了某些工作宏胯,則會顯得不太必要,因?yàn)榕c常規(guī)的功能相比本姥,組件的關(guān)注點(diǎn)會更多一點(diǎn)肩袍。
較新的瀏覽器(和React Native)實(shí)現(xiàn)了有助于解決此確切問題的API:equestIdleCallback安排在空閑期間調(diào)用的低優(yōu)先級函數(shù),而requestAnimationFrame安排在下一個(gè)動畫幀上調(diào)用的高優(yōu)先級函數(shù)婚惫。問題在于氛赐,想要使用這些API,你需要一種將render分解為增量單位的方法先舷,否則會一直執(zhí)行到堆棧為空艰管。
如果我們可以自定義調(diào)用堆棧的行為來優(yōu)化UI展現(xiàn),是不是特別棒蒋川?如果我們可以隨意調(diào)用堆棧蛙婴,并且手動操作堆棧,是不是更棒?
這就是React Fiber的目標(biāo)街图,F(xiàn)iber是堆棧的重新實(shí)現(xiàn)浇衬,也可以將其視為虛擬堆棧 。
重新實(shí)現(xiàn)堆棧的優(yōu)勢在于餐济,你可以將堆棧保存在內(nèi)存中耘擂,并根據(jù)需要(以及任何時(shí)候)執(zhí)行他們,這對于我們的目標(biāo)來說至關(guān)重要絮姆。
除了任務(wù)調(diào)度之外醉冤,手動處理堆棧還可以釋放并發(fā)和錯(cuò)誤邊界等功能。這些主題在以后的章節(jié)中會介紹篙悯。
下一節(jié)中蚁阳,我們更多的來研究一下Fiber的結(jié)構(gòu)。
Fiber的結(jié)構(gòu)
注意:隨著我們對實(shí)現(xiàn)細(xì)節(jié)的更加具體化鸽照,某些事項(xiàng)改變的可能性也增加了螺捐,如果發(fā)現(xiàn)任何錯(cuò)誤或者過時(shí)的信息,請?zhí)峤籔R矮燎。
具體來說定血,F(xiàn)iber是一個(gè)JavaScript的對象,其中包含有關(guān)組件诞外,以及輸入和輸出的信息澜沟。
Fiber類似于一個(gè)堆棧框架峡谊,但也對應(yīng)于一個(gè)組件的實(shí)例茫虽。
這里有一些Fiber的重要概念(包括但不限于)
type
和 key
在Fiber中,type和key的作用相同既们,就像React組件一樣(事實(shí)上濒析,創(chuàng)建一個(gè)元素的時(shí)候,F(xiàn)iber會復(fù)制這兩個(gè)字段)贤壁。
Fiber的type描述了他對一個(gè)的組件,對于復(fù)合組件埠忘,type是函數(shù)或者類組件本身脾拆,對于標(biāo)準(zhǔn)組件(例如div,span)莹妒,type是string名船。
從概念上來講,fiber是由堆椫嫉。框架執(zhí)行的函數(shù)(例如v = f(d)
)渠驼。
與type一起,key主要用來在reconciliation期間確定Fiber是否可重用鉴腻。
child
and sibling
這些字段指向其他Fiber迷扇,描述了Fiber的遞歸樹結(jié)構(gòu)百揭。
child fiber對應(yīng)組件的render的返回值,所以在下面代碼中蜓席,Parent
的fiber指向Child
function Parent() {
return <Child />
}
sibling
字段說明了返回多個(gè)子項(xiàng)的情況(Fiber中的新功能)器一。
function Parent() {
return [<Child1 />, <Child2 />]
}
child fiber在這種情況形成了一個(gè)一維列表,開頭是第一個(gè)子鏈厨内。所以在示例中祈秕,Parent
的child是Child1
,而Child1
的兄弟節(jié)點(diǎn)是Child2
回到之前的函數(shù)類比, 你可以把 child fiber當(dāng)成 尾部函數(shù)雏胃。
return
return fiber 是當(dāng)程序處理完當(dāng)前fiber之后返回的fiber请毛。從概念上講,它與堆棧幀的返回地址相同瞭亮,也可以將其視為父fiber方仿。
如果一個(gè)fiber具有多個(gè)子fiber,那么每個(gè)子fiber返回的都是其父fiber街州。因此上面的例子中Child1
和Child2
的 return fiber都是Parent
兼丰。
pendingProps
和 memoizedProps
從概念上來講,props是函數(shù)的參數(shù)唆缴,fiber的pendingProps
在執(zhí)行開始時(shí)設(shè)置鳍征,而memoizedProps
在結(jié)束的時(shí)候設(shè)置。
當(dāng)傳入的pendingProps
和memoizedProps
相同的時(shí)候面徽,表示fiber可以重新使用之前的fiber艳丛,以避免重復(fù)的工作。
pendingWorkPriority(待處理的工作優(yōu)先級)
Fiber的工作優(yōu)先級用數(shù)字來表示趟紊,翻閱 ReactPriorityLevel模塊可以查詢每個(gè)值所代表的含義氮双。
除NoWork為0外,數(shù)字越大表示優(yōu)先級越低霎匈。例如戴差,您可以使用以下功能來檢查Fiber的優(yōu)先級是否至少與給定級別一樣高:
function matchesPriority(fiber, priority) {
return fiber.pendingWorkPriority !== 0 &&
fiber.pendingWorkPriority <= priority
}
這只是一個(gè)說明示例,并非Fiber代碼庫的一部分
scheduler使用優(yōu)先級字段來查找下一個(gè)需要執(zhí)行的單元铛嘱,這個(gè)算法后面會討論暖释。
alternate
flush
fiber的flush是指將其輸出渲染到屏幕上。
work-in-progress
未完成的fiber墨吓,也就是為返回的堆棧幀球匕。
在任何時(shí)候,一個(gè)組件實(shí)例最多對應(yīng)兩個(gè)fiber:當(dāng)前的flushed fiber 和 work-in-progress fiber帖烘。
當(dāng)前fiber的備用fiber是work-in-progress fiber亮曹,反過來也是一樣。
fiber的備胎由cloneFiber
延遲創(chuàng)建,而且并非每次都創(chuàng)建一個(gè)新的對象照卦,cloneFiber
會嘗試重用fiber的備胎(如果有的話)式矫,從而最大程度的減少資源消耗。
alternate
字段可以理解成實(shí)現(xiàn)細(xì)節(jié)窄瘟,但是在庫中經(jīng)常出現(xiàn)衷佃,所以在這里說明一下也是很有意義的。
output
host component
React程序中的葉節(jié)點(diǎn)蹄葱,一般是指特定的渲染環(huán)境(例如在瀏覽器中就是div 氏义,span等),在jsx中都是用小寫標(biāo)簽名來表示图云。
fiber的輸出一般都是函數(shù)的返回值惯悠。
每個(gè)fiber都會有output,但是output只會由host component
在葉節(jié)點(diǎn)中創(chuàng)建竣况,并最終輸出到節(jié)點(diǎn)樹上克婶。
output是最終提供給渲染器的輸出,以便輸出到具體渲染環(huán)境(譯者注:react-dom 或者 react-native)中丹泉,如何定義輸出和輸入是渲染器的責(zé)任情萤。
Future sections(未來規(guī)劃)
目前只說這么多,但是這片文檔還遠(yuǎn)遠(yuǎn)不夠完整摹恨,以后的部分會描述在整個(gè)生命周期中使用的算法筋岛,涵蓋的主題包括以下:
- scheduler如何找到下一個(gè)需要執(zhí)行的工作單元
- 如何通過fiber樹來跟蹤和傳播優(yōu)先級
- scheduler怎么知道什么時(shí)候暫停或者繼續(xù)
- 如何刷新工作并標(biāo)記為完成
- side-effects (例如生命周期)如何運(yùn)作
- coroutine是什么以及如何用于實(shí)現(xiàn)上下文和布局等功能晒哄。
Related Videos
- [What's Next for React (ReactNext 2016)](
本文為原創(chuàng)文章睁宰,轉(zhuǎn)載請保留原出處。原文地址:https:/eatong.cn/blog/14