渲染機制 渲染機制主要分為兩部分: 首次渲染和更新渲染。 首次渲染 首先通過一個小例子,來講解首次渲染過程。 程式運行到 時,其中的 babel React.createElement(ClickCounter, null) element`如下: 接下來執行 函數,生成 節點。首先瞭解下 的部分數 ...
渲染機制
渲染機制主要分為兩部分: 首次渲染和更新渲染。
首次渲染
首先通過一個小例子,來講解首次渲染過程。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
import React from 'react';
import ReactDOM from 'react-dom';
class ClickCounter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
handleClick = () => {
this.setState((state) => {
return {count: state.count + 1};
});
}
render() {
return [
<button key="1" onClick={this.handleClick}>Update counter</button>,
<span key="2">{this.state.count}</span>,
]
}
}
ReactDOM.hydrate(<ClickCounter />, document.getElementById('root'));
程式運行到ReactDOM.hydrate
時,其中的<ClickCounter />
已被babel
轉換為React.createElement(ClickCounter, null)
,生成的element
如下:
{
$$typeof: Symbol(react.element),
key: null,
props: {},
ref: null,
type: ClickCounter
}
接下來執行hydrate
函數,生成root
節點。首先瞭解下fiber
的部分數據結構。
- alternate(對應的
workInProgress
或fiber
) - stateNode(關聯的
fiber
,組件實例或者DOM
節點) - type(組件或
HTML tag
,如div
,span
等) - tag(類型,詳見workTags)
- effectTag(操作類型,詳見sideEffectTag)
- updateQueue(更新隊列)
- memoizedState(
state
) - memoizedProps(
props
) - pendingProps(
VDOM
) - return(父
fiber
) - sibling(兄弟
fiber
) - child(孩子
fiber
) - firstEffect(第一個待處理的
effect fiber
) - lastEffect(最後一個待處理的
effect fiber
)
首次渲染會以同步渲染的方式進行渲染,首先創建一個update
,將element
裝載到其payload
屬性中,然後合併到root.current.updateQueue
,如果沒有updateQueue
會創建一個。我們暫且將root.current
看成HostRoot
。
接著根據HostRoot
克隆一棵workInProgress
更新樹。將HostRoot.alternate
指向workInProgress
,workInProgress.alternate
指向HostRoot
。然後進入workLoop
進行更新樹操作部分。workLoop
的任務也很簡單,就是將所有節點的更新掛載到更新樹上。下麵詳細看看reconciliation
階段。
reconciliation階段
reconciliation
的核心在於workLoop
。workLoop
會以workInProgress
為起點,即克隆的HostRoot
,不斷向下尋找。如果workInProgress.child
不為空,會進行diff
;如果為空會創建workInProgress.child`。
// 第一次迴圈nextUnitOfWork為workInProgress
function workLoop(isYieldy) {
if (!isYieldy) {
// Flush work without yielding
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until there's a higher priority event
while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
因為只涉及首次渲染,所以這裡將performUnitOfWork
簡單化。beginWork
根據workInProgress.tag
選擇不同的處理方式。先暫且看看如何處理HostRoot
。進入updateHostRoot
方法,先進行workInProgress.updateQueue
的更新,計算新的state
,將update.baseState
和workInProgress.memoizedState
指向新的state
。這裡新的state
裝載的是element
。
接下來調用createFiberFromElement
創建fiber
,將workInProgress.child
指向該fiber
,fiber.return
指向workInProgress
。
function performUnitOfWork(workInProgress) {
let next = beginWork(workInProgress); // 創建workInProgress.child並返回
if (next === null) { // 沒有孩子,收集effect list,返回兄弟或者父fiber
next = completeUnitOfWork(workInProgress);
}
return next;
}
function beginWork(workInProgress) {
switch(workInProgress.tag) {
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
case ClassComponent:
...
}
}
用一張圖體現更新樹創建完成後的樣子:
當workInProgress
沒有孩子時,即創建的孩子為空。說明已經到達底部,開始收集effect
。
function completeUnitOfWork(workInProgress) {
while (true) {
let returnFiber = workInProgress.return;
let siblingFiber = workInProgress.sibling;
nextUnitOfWork = completeWork(workInProgress);
...// 省略收集effect list過程
if (siblingFiber !== null) {
// If there is a sibling, return it
// to perform work for this sibling
return siblingFiber;
} else if (returnFiber !== null) {
// If there's no more work in this returnFiber,
// continue the loop to complete the parent.
workInProgress = returnFiber;
continue;
} else {
// We've reached the root.
return null;
}
}
}
function completeWork(workInProgress) {
//根據workInProgress.tag創建、更新或刪除dom
switch(workInProgress.tag) {
case HostComponent:
...
}
return null;
}
協調演算法過程結束後,workInProgress
更新樹更新完畢,收集的effect list
如下:
commit階段
effect list
(鏈表)是reconciliation
階段的結果,決定了哪些節點需要插入、更新和刪除,以及哪些組件需要調用生命周期函數。firstEffect
記錄第一個更新操作,firstEffect.nextEffect(fiber)
記錄下一個,然後繼續通過其nextEffect
不斷往下尋找直至為null
。下麵是commit階段的主要流程:
// finishedWork為更新樹
function commitRoot(root, finishedWork) {
commitBeforeMutationLifecycles();
commitAllHostEffects();
root.current = finishedWork;
commitAllLifeCycles();
}
變數nextEffect
每次執行完上面一個函數會被重置為finishedWork
。
commitBeforeMutationLifecycles
檢查effect list
中每個fiber
是否有Snapshot effect
,如果有則執行getSnapshotBeforeUpdate
。
// 觸發getSnapshotBeforeUpdate
function commitBeforeMutationLifecycles() {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
if (effectTag & Snapshot) {
const current = nextEffect.alternate;
commitBeforeMutationLifeCycles(current, nextEffect);
}
nextEffect = nextEffect.nextEffect;
}
}
commitAllHostEffects
提交所有effect
,實現dom
的替換、更新和刪除。
function commitAllHostEffects() {
while(nextEffect !== null) {
var effectTag = nextEffect.effectTag;
var primaryEffectTag = effectTag & (Placement | Update | Deletion);
switch (primaryEffectTag) {
case Placement: {
commitPlacement(nextEffect);
...
}
case PlacementAndUpdate: {
commitPlacement(nextEffect);
var _current = nextEffect.alternate;
commitWork(_current, nextEffect);
...
}
case Update: {
var _current2 = nextEffect.alternate;
commitWork(_current2, nextEffect);
...
}
case Deletion: {// 觸發componentWillUnmout
commitDeletion(nextEffect);
...
}
}
nextEffect = nextEffect.nextEffect;
}
}
commitAllLifeCycles
觸發componentDidMount
或componentDidUpdate
function commitAllLifeCycles(finishedRoot, committedExpirationTime) {
while (nextEffect !== null) {
var effectTag = nextEffect.effectTag;
if (effectTag & (Update | Callback)) {
var current$$1 = nextEffect.alternate;
commitLifeCycles(finishedRoot, current$$1, nextEffect, committedExpirationTime);
}
if (effectTag & Ref) {
commitAttachRef(nextEffect);
}
if (effectTag & Passive) {
rootWithPendingPassiveEffects = finishedRoot;
}
nextEffect = nextEffect.nextEffect;
}
}
總結
這裡並未逐一細說,不想讀起來直犯困,更多講述了大概流程。如果覺得有疑惑的地方,也知道該在什麼地方找到對應的源碼,解答疑惑。
更好的閱讀體驗在我的github,歡迎