渲染機制
渲染機制主要分為兩部分: 首次渲染和更新渲染。
首次渲染
首先通過一個小例子,來講解首次渲染過程。
<!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,歡迎👏提issue。
