漸進式源碼解析React更新流程驅(qū)動
正文
前面兩篇文章介紹了fiber架構(gòu)和workLoop如何調(diào)度。但是缺了一塊非常重要的地方,那就是開發(fā)者寫的代碼是如何對接到上面流程的?
在日常開發(fā)中,我們只關(guān)心了如何寫組件,但是寫完的組件是如何被渲染到頁面當(dāng)中?又是如何驅(qū)動更新流程?如果不知道這塊內(nèi)容,其實大家還是云里霧里的停留在概念層面。
所以這篇文章主要是從開發(fā)者角度闡述開發(fā)代碼->編譯->觸發(fā)更新流程的介紹。
這里的更新流程指schedule(調(diào)度)->reconciler(協(xié)調(diào))->commit (渲染)
老規(guī)矩還是先制定5個小目標(biāo):
- react組件編譯成什么了?
- reactElement元素是什么?
- 什么是雙緩存技術(shù)?
- react.createRoot().render()做了什么事情?
- 還有哪些更新方式對接到目前的更新流程當(dāng)中?
ok,我們一一解答:
一、react.createElement和ReactElement元素
首先我們書寫的函數(shù)式組件、類組件、jsx等代碼全部會被babel-react編譯成react.createElement()的調(diào)用或者jsx()調(diào)用(取決于react版本)。
舉個栗子:
<div>
<ul>
<li key='1'>1</li>
<li key='2'>2</li>
<li key='3'>3</li>
</ul>
</div>
轉(zhuǎn)換成
React.createElement(
'div',
null,
React.createElement(
'ul',
null,
React.createElement(
'li',
{
key: '1'
},
'1'
),
React.createElement(
'li',
{
key: '2'
},
'2'
),
React.createElement(
'li',
{
key: '3'
},
'3'
)
)
);
接下來我們需要知道React.createElement內(nèi)部到底做了什么?源碼位置
內(nèi)部的實現(xiàn)其實很簡單,就是處理傳入的type/config/children等參數(shù),再返回一個新的對象。
- 從config中分離出特殊屬性 key 和 ref
- 將普通屬性以及children添加到props中
- 最后返回一個對象,這個對象我們稱之為ReactElement元素
ReactElement數(shù)據(jù)結(jié)構(gòu)如下:
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref,
props,
};
- '$$typeof':ReactElement的標(biāo)識
- 'type':可能是'div' 'span'這樣的字符串標(biāo)簽,也可以是個函數(shù)(函數(shù)式組件)、類(類組件)
- 'key/ref/props': ReactElement的屬性
所以上述栗子的調(diào)用結(jié)果是下面的樹形結(jié)構(gòu):
{
type: 'div',
key: null,
ref: null,
props: {
children: {
type: 'ul',
key: null,
ref: null,
props: {
children: [
{
type: 'li',
key: null,
ref: null,
props: {
children: '1'
}
},
{
type: 'li',
key: null,
ref: null,
props: {
children: '2'
}
},
{
type: 'li',
key: null,
ref: null,
props: {
children: '3'
}
}
]
}
}
}
}
到這里就已經(jīng)完成第一個和第二個小目標(biāo)。
不過在這里要多提一下,上述的樹形結(jié)構(gòu),在react15版本及以前就可以直接拿來diff以及生成頁面,不過正如第一篇文章所說,這樣會遇到很大的問題(任務(wù)過重js執(zhí)行時間久,影響渲染)。
所以16之后做的事情,就是依據(jù)上述的樹形結(jié)構(gòu)進行重構(gòu),重構(gòu)出來的fiber數(shù)據(jù)結(jié)構(gòu)用于滿足異步渲染之需。
二、雙緩存技術(shù)
上篇文章中已經(jīng)介紹了fiber節(jié)點的數(shù)據(jù)結(jié)構(gòu),這里我們再介紹下fiberRoot以及rootFiber。 fiberRoot源碼位置
FiberRoot數(shù)據(jù)結(jié)構(gòu):
class FiberRootNode {
current: FiberNode;
container: any | null;
finishedWork: FiberNode | null;
pendingLanes: Lanes;
finishedLane: Lane;
pendingPassiveEffects: PendingPassiveEffects;
constructor(container: any | null, hostRootFiber: FiberNode) {
this.current = hostRootFiber;
this.container = container;
hostRootFiber.stateNode = this;
this.finishedWork = null;
this.pendingLanes = NoLanes;
this.finishedLane = NoLane;
this.pendingPassiveEffects = {
unmount: [],
update: []
};
}
}
其中很多屬性我們暫時無視,后續(xù)涉及到的時候會詳細(xì)講解,這里重點關(guān)注節(jié)點的關(guān)系。 rootFiber的數(shù)據(jù)結(jié)構(gòu)和普通的FiberNode節(jié)點區(qū)別不大,這里不再贅述~
整個React應(yīng)用有且只有一個fiberRoot
整個應(yīng)用中同時存在兩棵rootFiber樹
當(dāng)前頁面對應(yīng)的稱為currentFiber,另外一顆在內(nèi)存中構(gòu)建的稱為workInProgressFiber,它們通過alternate屬性連接。
fiberRoot中的current指針指向了currentFiber樹。
當(dāng)整個應(yīng)用更新完成,fiberRoot會修改current指針指向內(nèi)存中構(gòu)建好的workInProgressFiber。
圖形描述如下:

以上我們稱之為雙緩存技術(shù),當(dāng)然這個技術(shù)不光用在react中,其他很多地方都有涉及,大家感興趣的話自行Google。
三、React初始化的執(zhí)行函數(shù)
在mount階段的時候,應(yīng)用是需要一個執(zhí)行函數(shù)的,而這個函數(shù)就是(源碼位置)
react.createRoot(root).render(<App/>)
root: 模版文件中的id為root的div<App>: 整個應(yīng)用的根組件
源碼簡化后的代碼如下:
const createRoot = (container: Container) => {
const root = createContainer(container);
return {
render(element: ReactElementType) {
return updateContainer(element, root);
}
};
};
createRoot會返回一個對象,其中包含了render函數(shù),我們具體看看createContainer做了哪些事情。
const createContainer = (container: Container) => {
// 創(chuàng)建rootFiber
const hostRootFiber = new FiberNode(HostRoot, {}, null);
// 創(chuàng)建fiberRoot
const root = new FiberRootNode(container, hostRootFiber);
hostRootFiber.updateQueue = createUpdateQueue();
return root;
};
react.createRoot()在內(nèi)部會去創(chuàng)建整個應(yīng)用唯一的fiberRoot和rootFiber,并進行關(guān)聯(lián)。(如上述圖形結(jié)構(gòu))
render內(nèi)部執(zhí)行的是updateContainer(),我們查看下內(nèi)部實現(xiàn):
const updateContainer = (
element: ReactElementType,
root: FiberRootNode
) => {
// mount時
const hostRootFiber = root.current;
// 添加update任務(wù)
const lane = requestUpdateLane();
const update = createUpdate<ReactElementType | null>(element, lane);
enqueueUpdate(
hostRootFiber?.updateQueue as UpdateQueue<ReactElementType | null>,
update
);
scheduleUpdateOnFiber(hostRootFiber, lane);
return element;
};
其中有很多地方我們此時無須關(guān)心,但是我們看到內(nèi)部調(diào)用了scheduleUpdateOnFiber, 而這個就是更新流程(schedule(調(diào)度)->reconciler(協(xié)調(diào))->commit (渲染))的入口。
而這個入口不僅僅在初始化執(zhí)行函數(shù)中render調(diào)用會喚起,還有其他的方式:
- 類組件中setState -> scheduleUpdateOnFiber()
- 函數(shù)組件useState -> scheduleUpdateOnFiber()
至此,我們知道了開發(fā)代碼->編譯->觸發(fā)更新流程的鏈路。
ok,以上就是文章的所有內(nèi)容了,我們總結(jié)下:
- react組件會被編譯成react.createElement的調(diào)用,而調(diào)用結(jié)果是一顆樹形結(jié)構(gòu)。
- react16之后會重構(gòu)增強這顆樹,變成fiber結(jié)構(gòu)。
- 在react應(yīng)用中,使用了雙緩存技術(shù),用于更新。
- mount階段的執(zhí)行函數(shù)(createRoot().render)會創(chuàng)建fiberRoot并且喚起更新流程。
- 更新流程的喚起還有setState、useState等方式。
此篇文章完成了5個小目標(biāo),相信大家對整體的鏈路會更加清晰,后續(xù)的文章,會進一步深入到具體實現(xiàn)當(dāng)中,敬請期待~
以上就是漸進式源碼解析React更新流程驅(qū)動的詳細(xì)內(nèi)容,更多關(guān)于React更新流程驅(qū)動的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用react實現(xiàn)手機號的數(shù)據(jù)同步顯示功能的示例代碼
本篇文章主要介紹了使用react實現(xiàn)手機號的數(shù)據(jù)同步顯示功能的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04
Objects are not valid as a Rea
這篇文章主要為大家介紹了Objects are not valid as a React child報錯解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12

