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-12react-router browserHistory刷新頁面404問題解決方法
本篇文章主要介紹了react-router browserHistory刷新頁面404問題解決方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-12-12react PropTypes校驗(yàn)傳遞的值操作示例
這篇文章主要介紹了react PropTypes校驗(yàn)傳遞的值操作,結(jié)合實(shí)例形式分析了react PropTypes針對(duì)傳遞的值進(jìn)行校驗(yàn)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2020-04-04react hooks實(shí)現(xiàn)防抖節(jié)流的方法小結(jié)
這篇文章主要介紹了react hooks實(shí)現(xiàn)防抖節(jié)流的幾種方法,文中通過代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-04-04