漸進式源碼解析React更新流程驅動
正文
前面兩篇文章介紹瞭fiber架構和workLoop如何調度。但是缺瞭一塊非常重要的地方,那就是開發者寫的代碼是如何對接到上面流程的?
在日常開發中,我們隻關心瞭如何寫組件,但是寫完的組件是如何被渲染到頁面當中?又是如何驅動更新流程?如果不知道這塊內容,其實大傢還是雲裡霧裡的停留在概念層面。
所以這篇文章主要是從開發者角度闡述開發代碼->編譯->觸發更新流程的介紹。
這裡的更新流程指schedule(調度)->reconciler(協調)->commit (渲染)
老規矩還是先制定5個小目標:
- react組件編譯成什麼瞭?
- reactElement元素是什麼?
- 什麼是雙緩存技術?
- react.createRoot().render()做瞭什麼事情?
- 還有哪些更新方式對接到目前的更新流程當中?
ok,我們一一解答:
一、react.createElement和ReactElement元素
首先我們書寫的函數式組件、類組件、jsx等代碼全部會被babel-react編譯成react.createElement()的調用或者jsx()調用(取決於react版本)。
舉個栗子:
<div> <ul> <li key='1'>1</li> <li key='2'>2</li> <li key='3'>3</li> </ul> </div>
轉換成
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內部到底做瞭什麼?源碼位置
內部的實現其實很簡單,就是處理傳入的type/config/children等參數,再返回一個新的對象。
- 從config中分離出特殊屬性 key 和 ref
- 將普通屬性以及children添加到props中
- 最後返回一個對象,這個對象我們稱之為ReactElement元素
ReactElement數據結構如下:
const element = { $$typeof: REACT_ELEMENT_TYPE, type, key, ref, props, };
- '$$typeof':ReactElement的標識
- 'type':可能是'div' 'span'這樣的字符串標簽,也可以是個函數(函數式組件)、類(類組件)
- 'key/ref/props': ReactElement的屬性
所以上述栗子的調用結果是下面的樹形結構:
{ 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' } } ] } } } }
到這裡就已經完成第一個和第二個小目標。
不過在這裡要多提一下,上述的樹形結構,在react15版本及以前就可以直接拿來diff以及生成頁面,不過正如第一篇文章所說,這樣會遇到很大的問題(任務過重js執行時間久,影響渲染)。
所以16之後做的事情,就是依據上述的樹形結構進行重構,重構出來的fiber數據結構用於滿足異步渲染之需。
二、雙緩存技術
上篇文章中已經介紹瞭fiber節點的數據結構,這裡我們再介紹下fiberRoot以及rootFiber。 fiberRoot源碼位置
FiberRoot數據結構:
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: [] }; } }
其中很多屬性我們暫時無視,後續涉及到的時候會詳細講解,這裡重點關註節點的關系。 rootFiber的數據結構和普通的FiberNode節點區別不大,這裡不再贅述~
整個React應用有且隻有一個fiberRoot
整個應用中同時存在兩棵rootFiber樹
當前頁面對應的稱為currentFiber,另外一顆在內存中構建的稱為workInProgressFiber,它們通過alternate屬性連接。
fiberRoot中的current指針指向瞭currentFiber樹。
當整個應用更新完成,fiberRoot會修改current指針指向內存中構建好的workInProgressFiber。
圖形描述如下:
以上我們稱之為雙緩存技術,當然這個技術不光用在react中,其他很多地方都有涉及,大傢感興趣的話自行Google。
三、React初始化的執行函數
在mount階段的時候,應用是需要一個執行函數的,而這個函數就是(源碼位置)
react.createRoot(root).render(<App/>)
root
: 模版文件中的id為root的div<App>
: 整個應用的根組件
源碼簡化後的代碼如下:
const createRoot = (container: Container) => { const root = createContainer(container); return { render(element: ReactElementType) { return updateContainer(element, root); } }; };
createRoot會返回一個對象,其中包含瞭render函數,我們具體看看createContainer做瞭哪些事情。
const createContainer = (container: Container) => { // 創建rootFiber const hostRootFiber = new FiberNode(HostRoot, {}, null); // 創建fiberRoot const root = new FiberRootNode(container, hostRootFiber); hostRootFiber.updateQueue = createUpdateQueue(); return root; };
react.createRoot()在內部會去創建整個應用唯一的fiberRoot和rootFiber,並進行關聯。(如上述圖形結構)
render內部執行的是updateContainer(),我們查看下內部實現:
const updateContainer = ( element: ReactElementType, root: FiberRootNode ) => { // mount時 const hostRootFiber = root.current; // 添加update任務 const lane = requestUpdateLane(); const update = createUpdate<ReactElementType | null>(element, lane); enqueueUpdate( hostRootFiber?.updateQueue as UpdateQueue<ReactElementType | null>, update ); scheduleUpdateOnFiber(hostRootFiber, lane); return element; };
其中有很多地方我們此時無須關心,但是我們看到內部調用瞭scheduleUpdateOnFiber, 而這個就是更新流程(schedule(調度)->reconciler(協調)->commit (渲染))的入口。
而這個入口不僅僅在初始化執行函數中render調用會喚起,還有其他的方式:
- 類組件中setState -> scheduleUpdateOnFiber()
- 函數組件useState -> scheduleUpdateOnFiber()
至此,我們知道瞭開發代碼->編譯->觸發更新流程的鏈路。
ok,以上就是文章的所有內容瞭,我們總結下:
- react組件會被編譯成react.createElement的調用,而調用結果是一顆樹形結構。
- react16之後會重構增強這顆樹,變成fiber結構。
- 在react應用中,使用瞭雙緩存技術,用於更新。
- mount階段的執行函數(createRoot().render)會創建fiberRoot並且喚起更新流程。
- 更新流程的喚起還有setState、useState等方式。
此篇文章完成瞭5個小目標,相信大傢對整體的鏈路會更加清晰,後續的文章,會進一步深入到具體實現當中,敬請期待~
以上就是漸進式源碼解析React更新流程驅動的詳細內容,更多關於React更新流程驅動的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- React團隊測試並發特性詳解
- 一起來學習React元素的創建和渲染
- 一篇文章帶你理解React Props的 原理
- React中的JSX { }的使用
- React 的調和算法Diffing 算法策略詳解