React18系列reconciler從0實(shí)現(xiàn)過程詳解
引言
本系列是講述從0開始實(shí)現(xiàn)一個(gè)react18的基本版本。由于React源碼通過Mono-repo 管理倉庫,我們也是用pnpm提供的workspaces來管理我們的代碼倉庫,打包我們使用rollup進(jìn)行打包。
我們這一次主要寫有關(guān)調(diào)和(reconciler)和ReactDom,React將調(diào)和單獨(dú)的抽出一個(gè)包,暴露出入口,通過不同的宿主環(huán)境去調(diào)用不同的api。
React-Dom包
這個(gè)包主要是提供瀏覽器環(huán)境的一些dom操作。主要是提供2個(gè)文件hostConfig.ts 以及root.ts。 想想我們?cè)赗eact18中,是通過如下方式調(diào)用的。所以我們需要提供一個(gè)方法createRoot方法,返回要給包含render函數(shù)的對(duì)象。
import ReactDOM from 'react-dom/client'; ReactDOM.createRoot(root).render(<App />)
createRoot
主要功能是2個(gè),第一個(gè)是創(chuàng)建根fiberNode節(jié)點(diǎn), 第二個(gè)創(chuàng)建更新(初始化主要是用于渲染),開始調(diào)度。
//createRoot.ts 文件
import {
createContainer,
updateContainer,
} from "../../react-reconciler/src/filerReconciler";
export function createRoot(container: Container) {
const root = createContainer(container);
return {
render(element: ReactElementType) {
updateContainer(element, root);
},
};
}
createRoot.js主要是調(diào)用的react-reconciler的createContainer方法和updateContainer方法。我們之后看看這2個(gè)方法主要的作用
hostConfig.ts
主要是創(chuàng)建各種dom,已經(jīng)dom的插入操作
export const createInstance = (type: string, props: any): Instance => {
// TODO 處理props
const element = document.createElement(type);
return element;
};
export const appendInitialChild = (
parent: Instance | Container,
child: Instance
) => {
parent.appendChild(child);
};
export const createTextInstance = (content: string) => {
return document.createTextNode(content);
};
export const appendChildToContainer = appendInitialChild;
React-reconciler包
createContainer() 函數(shù)
從上面我們可以知道,首先調(diào)用的createContainer和updateContainer,我們把它寫到filerReconciler.ts中createContainer接受傳入的dom元素。
/**
* ReactDOM.createRoot()中調(diào)用
* 1. 創(chuàng)建fiberRootNode 和 hostRootFiber。并建立聯(lián)系
* @param {Container} container
*/
export function createContainer(container: Container) {
const hostRootFiber = new FiberNode(HostRoot, {}, null);
const fiberRootNode = new FiberRootNode(container, hostRootFiber);
hostRootFiber.updateQueue = createUpdateQueue();
return fiberRootNode;
}
可以看到我們?cè)谶@里主要就是2個(gè)事情
調(diào)用了2個(gè)方法去創(chuàng)建2個(gè)不同的fiberNode,一個(gè)是hostRootFiber,一個(gè)是fiberRootNode
創(chuàng)建一個(gè)更新隊(duì)列,并將其賦值給hostRootFiber
/**
* 頂部節(jié)點(diǎn)
*/
export class FiberRootNode {
container: Container; // 不同環(huán)境的不同的節(jié)點(diǎn) 在瀏覽器環(huán)境 就是 root節(jié)點(diǎn)
current: FiberNode;
finishedWork: FiberNode | null; // 遞歸完成后的hostRootFiber
constructor(container: Container, hostRootFiber: FiberNode) {
this.container = container;
this.current = hostRootFiber;
hostRootFiber.stateNode = this;
this.finishedWork = null;
}
}
export class FiberNode {
constructor(tag: WorkTag, pendingProps: Props, key: Key) {
this.tag = tag;
this.pendingProps = pendingProps;
this.key = key;
this.stateNode = null; // dom引用
this.type = null; // 組件本身 FunctionComponent () => {}
// 樹狀結(jié)構(gòu)
this.return = null; // 指向父fiberNode
this.sibling = null; // 兄弟節(jié)點(diǎn)
this.child = null; // 子節(jié)點(diǎn)
this.index = 0; // 兄弟節(jié)點(diǎn)的索引
this.ref = null;
// 工作單元
this.pendingProps = pendingProps; // 等待更新的屬性
this.memoizedProps = null; // 正在工作的屬性
this.memoizedState = null;
this.updateQueue = null;
this.alternate = null; // 雙緩存樹指向(workInProgress 和 current切換)
this.flags = NoFlags; // 副作用標(biāo)識(shí)
this.subtreeFlags = NoFlags; // 子樹中的副作用
}
}

接下來,我們看看createUpdateQueue里面的執(zhí)行邏輯。執(zhí)行了一個(gè)函數(shù),返回了一個(gè)對(duì)象。所以現(xiàn)在hostRootFiber的updateQueue指向了這個(gè)指針
/**
* 初始化updateQueue
* @returns {UpdateQueue<Action>}
*/
export const createUpdateQueue = <State>() => {
return {
shared: {
pending: null,
},
} as UpdateQueue<State>;
};
我們從上面createRoot執(zhí)行完后,返回了一個(gè)render函數(shù),我們接下來看看render后的執(zhí)行過程,是怎么渲染到頁面的。
render() 調(diào)用
createRoot執(zhí)行后,創(chuàng)建了一個(gè)rootFiberNode, 并返回了render調(diào)用,主要是執(zhí)行了updateContainer用于去渲染初始化的工作。
updateContainer接受2個(gè)參數(shù),第一個(gè)參數(shù)是傳入的ReactElement(), 第二個(gè)參數(shù)是fiberRootNode。
主要是做3件事情:
- 創(chuàng)建一個(gè)更新事件
- 把更新事件推進(jìn)隊(duì)列中
- 調(diào)用調(diào)度,開始更新
/**
* ReactDOM.createRoot().render 中調(diào)用更新
* 1. 創(chuàng)建update, 并將其推到enqueueUpdate中
*/
export function updateContainer(
element: ReactElementType | null,
root: FiberRootNode
) {
const hostRootFiber = root.current;
const update = createUpdate<ReactElementType | null>(element);
enqueueUpdate(
hostRootFiber.updateQueue as UpdateQueue<ReactElementType | null>,
update
);
// 插入更新后,進(jìn)入調(diào)度
scheduleUpdateOnFiber(hostRootFiber);
return element;
}
創(chuàng)建更新createUpdate
實(shí)際上就是創(chuàng)建一個(gè)對(duì)象,由于初始化的時(shí)候傳入的是ReactElementType(), 所以返回的是App對(duì)應(yīng)的ReactElement對(duì)象
/**
* 創(chuàng)建更新
* @param {Action<State>} action
* @returns {Update<State>}
*/
export const createUpdate = (action) => {
return {
action,
};
};
將更新推進(jìn)隊(duì)列enqueueUpdate
接受2個(gè)參數(shù),第一個(gè)參數(shù)是我們創(chuàng)建一個(gè)更新隊(duì)列的引用,第二個(gè)是新增的隊(duì)列
/**
* 更新update
* @param {UpdateQueue<Action>} updateQueue
* @param {Update<Action>} update
*/
export const enqueueUpdate = <State>(
updateQueue: UpdateQueue<State>,
update: Update<State>
) => {
updateQueue.shared.pending = update;
};
執(zhí)行到這一步驟,我們得到了更新隊(duì)列,其實(shí)是一個(gè)ReactElement組件 及我們調(diào)用render傳入的jsx對(duì)象。
開始調(diào)用scheduleUpdateOnFiber
接受FiberNode開始執(zhí)行我們的渲染工作, 一開始渲染傳入的是hostFiberNode 之后其他更新傳遞的是對(duì)應(yīng)的fiberNode
export function scheduleUpdateOnFiber(fiber: FiberNode) {
// todo 調(diào)度功能
let root = markUpdateFromFiberToRoot(fiber);
renderRoot(root);
}
wookLoop
執(zhí)行完上面的操作后,接下來進(jìn)入的調(diào)和階段。開始我們要明白一個(gè)關(guān)鍵詞:
workInProgress: 表示當(dāng)前正在調(diào)和的fiber節(jié)點(diǎn),之后簡(jiǎn)稱wip
beginWork: 主要是根據(jù)當(dāng)前fiberNode創(chuàng)建下一級(jí)fiberNode,在update時(shí)標(biāo)記placement(新增、移動(dòng))ChildDeletion(刪除)
completeWork: 在mount時(shí)構(gòu)建Dom Tree, 初始化屬性,在Update時(shí)標(biāo)記Update(屬性更新),最終執(zhí)行flags冒泡
flags冒泡我們下一節(jié)講。
從上面我們可以看到調(diào)用了scheduleUpdateOnFiber方法,開始從根部渲染頁面。scheduleUpdateOnFiber主要是執(zhí)行了2個(gè)方法:
markUpdateFromFiberToRoot: 由于我們更新的節(jié)點(diǎn)可能不是hostfiberNode, 這個(gè)方法就是不管傳入的是那個(gè)節(jié)點(diǎn),返回我們的根節(jié)點(diǎn)rootFiberNode
// 從當(dāng)前觸發(fā)更新的fiber向上遍歷到根節(jié)點(diǎn)fiber
function markUpdateFromFiberToRoot(fiber: FiberNode) {
let node = fiber;
let parent = node.return;
while (parent !== null) {
node = parent;
parent = node.return;
}
if (node.tag === HostRoot) {
return node.stateNode;
}
return null;
}
renderRoot: 這里是我們wookLoop的入口,也是調(diào)和完成后,將生成的fiberNode樹,賦值給finishedWork,并掛在根節(jié)點(diǎn)上,進(jìn)入commit的入口。
function renderRoot(root: FiberRootNode) {
// 初始化,將workInProgress 指向第一個(gè)fiberNode
prepareFreshStack(root);
do {
try {
workLoop();
break;
} catch (e) {
if (__DEV__) {
console.warn("workLoop發(fā)生錯(cuò)誤", e);
}
workInProgress = null;
}
} while (true);
const finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
// wip fiberNode樹 樹中的flags執(zhí)行對(duì)應(yīng)的操作
commitRoot(root);
}
prepareFreshStack函數(shù): 用于初始化當(dāng)前節(jié)點(diǎn)的wip, 并創(chuàng)建alternate 的雙緩存的建立。 由于我們開始的時(shí)候傳入的hostFiberNode, 經(jīng)過createWorkInProgress后,創(chuàng)建了一個(gè)新的fiberNode 并通過alternate相互指向。并賦值給wip
let workInProgress: FiberNode | null = null;
function prepareFreshStack(root: FiberRootNode) {
workInProgress = createWorkInProgress(root.current, {});
}
export const createWorkInProgress = (
current: FiberNode,
pendingProps: Props
): FiberNode => {
let wip = current.alternate;
if (wip === null) {
//mount
wip = new FiberNode(current.tag, pendingProps, current.key);
wip.stateNode = current.stateNode;
wip.alternate = current;
current.alternate = wip;
} else {
//update
wip.pendingProps = pendingProps;
// 清掉副作用(上一次更新遺留下來的)
wip.flags = NoFlags;
wip.subtreeFlags = NoFlags;
}
wip.type = current.type;
wip.updateQueue = current.updateQueue;
wip.child = current.child;
wip.memoizedProps = current.memoizedProps;
wip.memoizedState = current.memoizedState;
return wip;
};

接下來我們來分析一下workLoop中到底是如何生成fiberNode樹的。它本身函數(shù)執(zhí)行很簡(jiǎn)單。就是不停的根據(jù)wip進(jìn)行單個(gè)fiberNode的處理。 此時(shí)wip指向的hostRootFiber。開始執(zhí)行performUnitOfWork進(jìn)行遞歸操作,其中遞:beginWork,歸:completeWork。React通過DFS,首先找到對(duì)應(yīng)的葉子節(jié)點(diǎn)。
function workLoop() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(fiber: FiberNode): void {
const next = beginWork(fiber); // next 是fiber的子fiber 或者 是null
// 工作完成,需要將pendingProps 復(fù)制給 已經(jīng)渲染的props
fiber.memoizedProps = fiber.pendingProps;
if (next === null) {
// 沒有子fiber
completeUnitOfWork(fiber);
} else {
workInProgress = next;
}
}
beginWork開始
主要是向下進(jìn)行遍歷,創(chuàng)建不同的fiberNode。由于我們傳入的是HostRoot,所以會(huì)走到updateHostRoot分支
/**
* 遞歸中的遞階段
* 比較 然后返回子fiberNode 或者null
*/
export const beginWork = (wip: FiberNode) => {
switch (wip.tag) {
case HostRoot:
return updateHostRoot(wip);
case HostComponent:
return updateHostComponent(wip);
case HostText:
// 文本節(jié)點(diǎn)沒有子節(jié)點(diǎn),所以沒有流程
return null;
default:
if (__DEV__) {
console.warn("beginWork未實(shí)現(xiàn)的類型");
}
break;
}
return null;
};
updateHostRoot
這個(gè)方法主要是2個(gè)部分:
- 根據(jù)我們之前創(chuàng)建的更新隊(duì)列獲取到最新的值
- 創(chuàng)建子fiber
/**
processUpdateQueue: 是根據(jù)不同的類型(函數(shù)和其他)生成memoizedState
*/
function updateHostRoot(wip: FiberNode) {
const baseState = wip.memoizedState;
const updateQueue = wip.updateQueue as UpdateQueue<ElementType>;
// 這里獲取之前的更新隊(duì)列
const pending = updateQueue.shared.pending;
updateQueue.shared.pending = null;
const { memoizedState } = processUpdateQueue(baseState, pending); // 最新狀態(tài)
wip.memoizedState = memoizedState; // 其實(shí)就是傳入的element
const nextChildren = wip.memoizedState; // 就是我們傳入的ReactElement 對(duì)象
reconcileChildren(wip, nextChildren);
return wip.child;
}
reconcileChildren
調(diào)和子節(jié)點(diǎn), 根據(jù)是否生成過,分別調(diào)用不同的方法。通過上面我們知道傳入的hostFiber, 此時(shí)是存在alternate屬性的,所以會(huì)走到reconcilerChildFibers分支。
根據(jù)當(dāng)前傳入的returnFiber是hostFiberNode以及currentFiber為null,newChild為ReactElementType。我們可以判斷接下來會(huì)走到reconcileSingleElement的執(zhí)行。其中placeSingleChild是打標(biāo)記使用的,我們暫時(shí)先不研究。
/**
wip: 當(dāng)前正在執(zhí)行的父fiberNode
children: 即將要生成的子fiberNode
*/
function reconcileChildren(wip: FiberNode, children?: ReactElementType) {
const current = wip.alternate;
if (current !== null) {
// update
wip.child = reconcilerChildFibers(wip, current?.child, children);
} else {
// mount
wip.child = mountChildFibers(wip, null, children);
}
}
function reconcilerChildFibers(
returnFiber: FiberNode,
currentFiber: FiberNode | null,
newChild?: ReactElementType | string | number
) {
// 判斷當(dāng)前fiber的類型
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(returnFiber, currentFiber, newChild)
);
default:
if (__DEV__) {
console.warn("未實(shí)現(xiàn)的reconcile類型", newChild);
}
break;
}
}
// Todo 多節(jié)點(diǎn)的情況 ul > li * 3
// HostText
if (typeof newChild === "string" || typeof newChild === "number") {
return placeSingleChild(
reconcileSingleTextNode(returnFiber, currentFiber, newChild)
);
}
if (__DEV__) {
console.warn("未實(shí)現(xiàn)的reconcile類型", newChild);
}
return null;
};
}
reconcileSingleElement
從名字我們可以看出是通過ReactElement 創(chuàng)建單一的fiberNode。通過reconcileSingleElement我們就可以得出了一個(gè)新的子節(jié)點(diǎn),然后通過return指向父fiber。此時(shí)的fiberNode樹如下圖。
/**
* 根據(jù)reactElement對(duì)象創(chuàng)建fiber并返回
*/
function reconcileSingleElement(
returnFiber: FiberNode,
_currentFiber: FiberNode | null,
element: ReactElementType
) {
const fiber = createFiberFromElement(element);
fiber.return = returnFiber;
return fiber;
}
export function createFiberFromElement(element: ReactElementType): FiberNode {
const { type, key, props } = element;
let fiberTag: WorkTag = FunctionComponent;
if (typeof type === "string") {
// <div/> type : 'div'
fiberTag = HostComponent;
} else if (typeof type !== "function" && __DEV__) {
console.log("未定義的type類型", element);
}
const fiber = new FiberNode(fiberTag, props, key);
fiber.type = type;
return fiber;
}

調(diào)用完后,此時(shí)回到了reconcileChildren函數(shù)的這一句代碼執(zhí)行,指定wip的child指向。此時(shí)函數(shù)執(zhí)行完畢。
// 省略無關(guān)代碼
function reconcileChildren(wip: FiberNode, children?: ReactElementType) {
wip.child = reconcilerChildFibers(wip, current?.child, children);
}

執(zhí)行完后返回updateHostRoot函數(shù)調(diào)用reconcileChildren的地方。然后返回wip的child。
function updateHostRoot(wip) {
const baseState = wip.memoizedState;
reconcileChildren(wip, nextChildren);
return wip.child;
}
執(zhí)行完updateHostRoot函數(shù)后,返回調(diào)用它的beginWork中。beginWork也同樣返回了當(dāng)前wip的child節(jié)點(diǎn)。
export const beginWork = (wip: FiberNode) => {
switch (wip.tag) {
case HostRoot:
return updateHostRoot(wip);
}
}
執(zhí)行完后,我們最后又回到了最開始調(diào)用beginWork的地方。進(jìn)行接下來的操作,主要是將已經(jīng)渲染過的屬性賦值。然后將wip賦值給下一個(gè)剛剛生成的子節(jié)點(diǎn)。以便于開始下一次的遞歸中調(diào)用。
function performUnitOfWork(fiber) {
const next = beginWork(fiber); // next 是fiber的子fiber 或者 是null
// 工作完成,需要將pendingProps 復(fù)制給 已經(jīng)渲染的props
fiber.memoizedProps = fiber.pendingProps;
if (next === null) {
// 沒有子fiber
completeUnitOfWork(fiber);
}
else {
workInProgress = next;
}
}
由于workInProgress不等于null, 說明還有子節(jié)點(diǎn)。繼續(xù)進(jìn)行workLoop調(diào)用。又開始了新的一輪。直到我們到達(dá)了葉子節(jié)點(diǎn)。
function workLoop() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
例子
例如,如下例子,當(dāng)遍歷到hcc文本節(jié)點(diǎn)后,由于我們節(jié)點(diǎn)是沒有調(diào)和流程的。所以執(zhí)行到beginWork后,返回了一個(gè)null。正式結(jié)束了遞歸調(diào)用中的“遞" 過程。此時(shí)的fiberNode樹如下圖所示。
const jsx = <div><span>hcc</span></div>
const root = document.querySelector('#root')
ReactDOM.createRoot(root).render(jsx)

completeWork開始
從上面的beginWork操作后,此時(shí)我們wip在文本節(jié)點(diǎn)hcc的節(jié)點(diǎn)位置.
completeUnitOfWork
接下來執(zhí)行performUnitOfWork中的completeUnitOfWork的邏輯部分,我們看看completeUnitOfWork的邏輯部分。 我們傳入的最底部的葉子節(jié)點(diǎn)。首先會(huì)對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行completeWork的方法調(diào)用。
function completeUnitOfWork(fiber) {
let node = fiber;
do {
completeWork(node);
const sibling = node.sibling;
if (sibling !== null) {
workInProgress = sibling;
return;
}
node = node.return;
workInProgress = node;
} while (node !== null);
}
completeWork
首次我們會(huì)接受到一個(gè)最底部的子fiberNode,由于是第一次mount,所以當(dāng)前的fiber下不會(huì)存在alternate屬性的,所以會(huì)走到構(gòu)建Dom的流程。
/**
* 遞歸中的歸
*/
export const completeWork = (wip: FiberNode) => {
const newProps = wip.pendingProps;
const current = wip.alternate;
switch (wip.tag) {
case HostComponent:
if (current !== null && wip.stateNode) {
//update
} else {
// 1. 構(gòu)建DOM
const instance = createInstance(wip.type, newProps);
// 2. 將DOM插入到DOM樹中
appendAllChildren(instance, wip);
wip.stateNode = instance;
}
bubbleProperties(wip);
return null;
case HostText:
if (current !== null && wip.stateNode) {
//update
} else {
// 1. 構(gòu)建DOM
const instance = createTextInstance(newProps.content);
// 2. 將DOM插入到DOM樹中
wip.stateNode = instance;
}
bubbleProperties(wip);
return null;
case HostRoot:
bubbleProperties(wip);
return null;
default:
if (__DEV__) {
console.warn("未實(shí)現(xiàn)的completeWork");
}
break;
}
};
// 根據(jù)邏輯判斷,走到下面的邏輯判斷,傳入了文本
// 1. 構(gòu)建DOM
const instance = createTextInstance(newProps.content);
// 2. 將DOM插入到DOM樹中
wip.stateNode = instance;
經(jīng)過completeWork后,我們給當(dāng)前的wip添加了stateNode屬性,用于指向生成的Dom節(jié)點(diǎn)。 執(zhí)行完completeWork后,繼續(xù)返回到completeUnitOfWork中,查找sibling節(jié)點(diǎn),目前我們demo中沒有,所以會(huì)向上找到當(dāng)前節(jié)點(diǎn)的return指向。繼續(xù)執(zhí)行completeWork工作,此時(shí)的結(jié)構(gòu)變成了如下圖:

由于我們wip目前是HostComponent, 所以走到了如下的completeWork的邏輯。這里 根據(jù)type創(chuàng)建不同的Dom元素,和之前一樣,綁定到對(duì)應(yīng)的stateNode屬性上。我們可以看到除了這2個(gè),還執(zhí)行了一個(gè)函數(shù)appendAllChildren。我們?nèi)タ纯催@個(gè)函數(shù)的作用是什么
// 1. 構(gòu)建DOM const instance = createInstance(wip.type); // 2. 將DOM插入到DOM樹中 appendAllChildren(instance, wip); wip.stateNode = instance;
appendAllChildren
接受2個(gè)參數(shù),第一個(gè)是剛剛通過wip的type生成的對(duì)應(yīng)的dom, 另外一個(gè)是wip本身。 它的作用就是把我們上一步產(chǎn)生的dom節(jié)點(diǎn),插入到剛剛產(chǎn)生的父dom節(jié)點(diǎn)上,形成一個(gè)局部的小dom樹。
它本身存在一個(gè)復(fù)雜的遍歷過程,因?yàn)?code>fiberNode的層級(jí)和DOM元素的層級(jí)可能不是一一對(duì)應(yīng)的。
/**
* 在parent的節(jié)點(diǎn)下,插入wip
* @param {FiberNode} parent
* @param {FiberNode} wip
*/
function appendAllChildren(parent: Container, wip: FiberNode) {
let node = wip.child;
while (node !== null) {
if (node?.tag === HostComponent || node?.tag === HostText) {
appendInitialChild(parent, node?.stateNode);
} else if (node.child !== null) {
node.child.return = node;
// 繼續(xù)向下查找
node = node.child;
continue;
}
if (node === wip) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === wip) {
return;
}
// 向上找
node = node?.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
我們用這個(gè)圖來說明一下流程

- 當(dāng)前的”歸“到了
div對(duì)應(yīng)的fiberNode。我們獲取到node是第一個(gè)子元素的span, 執(zhí)行appendInitialChild方法,把對(duì)應(yīng)的stateNode的dom節(jié)點(diǎn)插入parent中。 - 接下來執(zhí)行由于
node.sibling不為空,所以會(huì)將node 復(fù)制給第二個(gè)span。然后繼續(xù)執(zhí)行appendInitialChild。以此執(zhí)行到第三個(gè)span節(jié)點(diǎn)。 - 第三個(gè)span節(jié)點(diǎn)對(duì)應(yīng)的
sibling為空,所以開始向上查找到node.return === wip結(jié)束函數(shù)調(diào)用。 - 此時(shí)三個(gè)span產(chǎn)生的dom,都已經(jīng)插入到
parent(div dom)中。
回到completeUnitOfWork
經(jīng)過上述操作后,我們繼續(xù)回到completeUnitOfWork的調(diào)用,繼續(xù)向上歸并。到上述例子的div節(jié)點(diǎn)。直到我們遍歷到hostFiberNode, 它是沒有return屬性的,所以返回null,結(jié)束了completeUnitOfWork的執(zhí)行?;氐搅俗铋_始的workLoop。此時(shí)的workInProgress等于null, 結(jié)束循環(huán)。
function workLoop() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
回到renderRoot
執(zhí)行完workLoop, 就回到了renderRoot的部分。此時(shí)我們已經(jīng)得到了完整的fiberNode樹,以及相應(yīng)的dom元素。此時(shí)對(duì)應(yīng)的結(jié)果如下圖:

那么生成的fiberNode樹是如何渲染的界面上的,我們下一章的commit章節(jié)介紹,如何打標(biāo)簽和渲染,更多關(guān)于React18系列reconciler實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React中的setState使用細(xì)節(jié)和原理解析(最新推薦)
這篇文章主要介紹了React中的setState使用細(xì)節(jié)和原理解析(最新推薦),前面我們有使用過setState的基本使用, 接下來我們對(duì)setState使用進(jìn)行詳細(xì)的介紹,需要的朋友可以參考下2022-12-12
react-router browserHistory刷新頁面404問題解決方法
本篇文章主要介紹了react-router browserHistory刷新頁面404問題解決方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-12-12
react PropTypes校驗(yàn)傳遞的值操作示例
這篇文章主要介紹了react PropTypes校驗(yàn)傳遞的值操作,結(jié)合實(shí)例形式分析了react PropTypes針對(duì)傳遞的值進(jìn)行校驗(yàn)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2020-04-04
react hooks實(shí)現(xiàn)防抖節(jié)流的方法小結(jié)
這篇文章主要介紹了react hooks實(shí)現(xiàn)防抖節(jié)流的幾種方法,文中通過代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-04-04

