React应用mount时全流程解析

您所在的位置:网站首页 react流程图 React应用mount时全流程解析

React应用mount时全流程解析

2023-09-04 22:37| 来源: 网络整理| 查看: 265

引言

前端框架React、Vue以及Angular已经三分天下数年。Angular因为版本升级造成的困扰,在国内已经几乎淡出前端人员的视野。可以说在国内是React和Vue在平分天下。而用过React的人都觉得它要比Vue香,它的声明式UI、用JavaScript编写组件间逻辑的灵活性以及大型应用渲染的速度都胜于Vue。

那React应用在mount时到底做了什么呢?本文从源码角度来解析它的执行逻辑和架构。

Top Level API

我们开发中必然少不了如下代码

import React, { Component } from 'react'; class Comp extends Component { onChange = () => { this.setState(...) } render () { return ... } }

React提供给我们的Top Level API 到目前的V17版本,一直都没变过。只是在V16.8时引入的HOOks的概念,但是并不影响我们开发。

这些Top Level API是如何实现的?

我们看一段React.Component的源码:

本文所有源码以V17.01版本为准。

// 本段源码在文件 packages/react/src/ReactBaseClasses.js 中 function Component(props, context, updater) { this.props = props; this.context = context; // 省略注释 this.refs = emptyObject; // 省略注释 this.updater = updater || ReactNoopUpdateQueue; } Component.prototype.isReactComponent = {}; // 省略注释 Component.prototype.setState = function(partialState, callback) { // ...省略部分代码 this.updater.enqueueSetState(this, partialState, callback, 'setState'); }; // 省略注释 Component.prototype.forceUpdate = function(callback) { this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); };

可见,我们经常使用的this.setState修改状态其实是执行了,updater.enqueueSetState方法。updater默认是ReactNoopUpdateQueue对象。

// 本段源码在文件 packages/react/src/ReactNoopUpdateQueue.js 中 /** * 这是一个抽象的API */ const ReactNoopUpdateQueue = { // 省略注释 isMounted: function(publicInstance) { return false; }, // 省略注释 enqueueForceUpdate: function(publicInstance, callback, callerName) { warnNoop(publicInstance, 'forceUpdate'); }, // 省略注释 enqueueReplaceState: function( publicInstance, completeState, callback, callerName, ) { warnNoop(publicInstance, 'replaceState'); }, // 省略注释 enqueueSetState: function( publicInstance, partialState, callback, callerName, ) { warnNoop(publicInstance, 'setState'); }, };

这下,我们清晰明了Top Level API在React中只是抽象层的API,并没有实现具体的功能。

那他具体的功能是在哪实现的呢?这个我们先卖个关子,并命名为 关子1⃣️。

其实Hooks也是这么设计的。我们平时使用的

import React, { useState } from 'react';

这些钩子函数在React中也都是抽象接口,并没有具体的功能。

感兴趣的可以去文件 packages/react/src/ReactHooks.js 中看看各个Hook的抽象实现。

render方法

我们都知道React渲染的入口是

ReactDOM.render(, document.getElementById('root'));

我们就看看render方法都做了什么。

render方法主要做了以下几件事:

给DOM对象root挂载 _reactRootContainer 私有属性 _reactRootContainer私有属性上挂载 _internalRoot 属性即FiberRootNode对象 创建HostRootFiber对象 赋值FiberRootNode对象的current指向HostRootFiber,赋值HostRootFiber的stateNode指向FiberRootNode 非批量更新模式下调用updateContainer方法生成完整的Fiber树

FiberRootNode和HostRootFiber(也叫RootFiber)的关系如图:

1.png

FiberRootNode

FiberRootNode也可以叫FiberRoot是整个应用的起点。

它也是一个JavaScript对象,承载了应用更新过程中的信息。

比如ContainerInfo是渲染容器的根结点即render的第二个参数。

current是当前应用的Fiber对象,即是RootFiber。

finishedWork是已经完成任务的Fiber对象,在commit阶段会处理它。

还有一些xxxxLines属性是和优先级有关。

HostRootFiber

HostRootFiber也叫RootFiber是Fiber对象,它是Fiber树的根节点。

Fiber是React的核心,它可以理解为虚拟DOM。

它主要有以下两层含义:

1、从静态的数据结构来说,每个Fiber节点对应一个React Element,保存组件的类型和对应的DOM节点信息

2、从动态的工作单元来说,每个Fiber节点保存更新状态下组件改变的状态以及DOM最终需要进行操作的行为比如被删除、被插入到页面、被更新属性等

我们看看Fiber对象的属性:

// 本段源码在文件 packages/react-reconciler/src/ReactFiber.old.js 中 function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) { /** --- 静态数据结构的属性 --- */ // 对应的组件类型 Function/Class/Host... this.tag = tag; // 即jsx的key属性 this.key = key; // 除特殊情况外,大部分情况与type相同 this.elementType = null; // 如果是函数组件它是函数本身,如果是类组件它是类对象,如果是HostComponent它的DOM节点 this.type = null; // 对应的真实DOM节点 this.stateNode = null; /** --- 用于连接其他fiber节点形成fiber树 --- */ // 父节点 this.return = null; // 第一个子节点 this.child = null; // 兄弟节点 this.sibling = null; this.index = 0; this.ref = null; /** --- 作为动态工作单元的属性 --- */ // 更新的props this.pendingProps = pendingProps; // 老的props this.memoizedProps = null; // 更新组成的链表,比如同时调用多个setState时 this.updateQueue = null; // 老的state以及计算后的形成的新的state this.memoizedState = null; this.dependencies = null; this.mode = mode; /** --- 本次更新造成的DOM操作 --- */ this.flags = NoFlags; this.nextEffect = null; this.firstEffect = null; this.lastEffect = null; this.subtreeFlags = NoFlags; this.deletions = null; // 调度优先级相关 this.lanes = NoLanes; this.childLanes = NoLanes; // 该fiber在另一次更新时对应的fiber this.alternate = null; // ...省略部分代码 }

从上面代码中我们可以知道,每个Fiber节点都对应一个React Element,多个Fiber节点如何构建Fiber树的呢?就是依赖下面三个属性:

// 父节点 this.return = null; // 第一个子节点 this.child = null; // 兄弟节点 this.sibling = null;

我们举个例子:

function App() { reutrn ( 闹闹前端 javascript nodejs ) }

对应的Fiber树结构:

2.png

好,现在对FiberRootNode、HostRootFiber以及Fiber树有了了解。

接下来就正式进入到Fiber树的创建过程即React的render阶段,即Fiber树的创建过程以及DOM树的构建过程。

render阶段

render阶段是Fiber树的创建过程以及DOM树的构建过程,不要和上面说的ReactDOM.render方法搞混了。

render阶段主要执行了两个方法即beginWork和completeUnitOfWork。

先简单说一下,进入beginWork之前的函数调用路线。

beginWork前

在ReactDOM.render方法中会 非批量更新模式下调用updateContainer方法生成完整的Fiber树

// 本段源码在文件 packages/react-reconciler/src/ReactFiberReconciler.old.js 中 export function updateContainer( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component, callback: ?Function, ): Lane { // ... 省略部分代码 enqueueUpdate(current, update); scheduleUpdateOnFiber(current, lane, eventTime); return lane; }

updateContainer方法内调用scheduleUpdateOnFiber方法。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中 export function scheduleUpdateOnFiber( fiber: Fiber, lane: Lane, eventTime: number, ) { // ... 省略部分代码 if (lane === SyncLane) { if ( // Check if we're inside unbatchedUpdates (executionContext & LegacyUnbatchedContext) !== NoContext && // Check if we're not already rendering (executionContext & (RenderContext | CommitContext)) === NoContext ) { // ... 省略部分代码 performSyncWorkOnRoot(root); } else { // ... 省略部分代码 } } else { // ... 省略部分代码 } // ... 省略部分代码 }

scheduleUpdateOnFiber方法内调用performSyncWorkOnRoot方法。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中 function performSyncWorkOnRoot(root) { // ... 省略部分代码 let lanes; let exitStatus; if ( root === workInProgressRoot && includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes) ) { // ... 省略部分代码 } else { lanes = getNextLanes(root, NoLanes); exitStatus = renderRootSync(root, lanes); } // ... 省略部分代码 // 此段代码进入commit阶段 const finishedWork: Fiber = (root.current.alternate: any); root.finishedWork = finishedWork; root.finishedLanes = lanes; commitRoot(root); // Before exiting, make sure there's a callback scheduled for the next // pending level. ensureRootIsScheduled(root, now()); return null; }

performSyncWorkOnRoot方法内部执行renderRootSync方法。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中 function renderRootSync(root: FiberRoot, lanes: Lanes) { // ... 省略部分代码 do { try { workLoopSync(); break; } catch (thrownValue) { handleError(root, thrownValue); } } while (true); // ... 省略部分代码 return workInProgressRootExitStatus; }

renderRootSync方法内部调用workLoopSync方法。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中 function workLoopSync() { while (workInProgress !== null) { performUnitOfWork(workInProgress); } }

这是个循环,workInProgress即为当前执行的Fiber。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中 function performUnitOfWork(unitOfWork: Fiber): void { const current = unitOfWork.alternate; setCurrentDebugFiberInDEV(unitOfWork); let next; if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) { startProfilerTimer(unitOfWork); next = beginWork(current, unitOfWork, subtreeRenderLanes); stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true); } else { next = beginWork(current, unitOfWork, subtreeRenderLanes); } resetCurrentDebugFiberInDEV(); unitOfWork.memoizedProps = unitOfWork.pendingProps; if (next === null) { // If this doesn't spawn new work, complete the current work. completeUnitOfWork(unitOfWork); } else { workInProgress = next; } ReactCurrentOwner.current = null; }

这里先调用beginWork,后调用completeUnitOfWork。

我们先关注一下beginWork的参数

current是unitOfWork.alternate即为当前fiber节点上一次更新后的fiber节点可以理解为老fiber节点 unitOfWork既是workInProgress,它是当前组件对应的fiber节点 SubtreeRenderLanes和优先级有关。本篇不涉及到优先级,所以这里忽略。 beginWork

beiginWork会根据传入的current是否存在,区别是mount还是update渲染。

在mount时,除了FiberRootNode外,其他的fiber节点的alternate肯定是null。

它会根据fiber.tag的不同,创建不同的子fiber节点。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberBeginWork.old.js 中 function beginWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ): Fiber | null { const updateLanes = workInProgress.lanes; // ...省略部分代码 // current存在即为update更新渲染,可以做一些优化比如复用current节点 if (current !== null) { // ...省略部分代码 return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); } else { // ...省略部分代码 } else { didReceiveUpdate = false; } // ...省略部分代码 // current === null即mount时 switch (workInProgress.tag) { // ...省略部分tag类型的处理代码 // 函数组件 case FunctionComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateFunctionComponent( current, workInProgress, Component, resolvedProps, renderLanes, ); } // 类组件 case ClassComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateClassComponent( current, workInProgress, Component, resolvedProps, renderLanes, ); } // ...省略部分tag类型的处理代码 } } // ...省略部分代码 }

可以看出,根据workInProgress.tag类型调用相应的updateXXX方法进行相应组件类型的处理。具体的tag类型可以参考文件 packages/react-reconciler/src/ReactWorkTags.js

我们以ClassComponent为例。

const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateClassComponent( current, workInProgress, Component, resolvedProps, renderLanes, );

先解析defaultProps,然后调用updateClassComponent方法。

updateClassComponent // 本段源码在文件 packages/react-reconciler/src/ReactFiberBeginWork.old.js 中 function updateClassComponent( current: Fiber | null, workInProgress: Fiber, Component: any, nextProps: any, renderLanes: Lanes, ) { // ...省略部分代码 const instance = workInProgress.stateNode; let shouldUpdate; if (instance === null) { if (current !== null) { // ...省略部分代码 } // mount时,执行 constructClassInstance(workInProgress, Component, nextProps); mountClassInstance(workInProgress, Component, nextProps, renderLanes); shouldUpdate = true; } else if (current === null) { // ...省略部分代码 } else { // ...省略部分代码 } const nextUnitOfWork = finishClassComponent( current, workInProgress, Component, shouldUpdate, hasContext, renderLanes, ); // ...省略部分代码 return nextUnitOfWork; }

mount时执行constructClassInstance即使用new运算符实例化此类组件同时设定累的updater对象,mountClassInstance调用前期的生命周期方法比如componentWillMount以及静态方法getDerivedStateFromProps。

接着调用finishClassComponent,它调用类对象上的render方法,进入Reconciler阶段,根据React Element创建对应的Fiber节点,并返回这个节点作为下一个执行的单元任务。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberClassComponent.old.js 中 function constructClassInstance( workInProgress: Fiber, ctor: any, props: any, ): any { // ...省略部分代码 // 实例化类组件 const instance = new ctor(props, context); const state = (workInProgress.memoizedState = instance.state !== null && instance.state !== undefined ? instance.state : null); adoptClassInstance(workInProgress, instance); // ...省略部分代码 return instance; }

这里的关键是adoptClassInstance方法。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberClassComponent.old.js 中 function adoptClassInstance(workInProgress: Fiber, instance: any): void { instance.updater = classComponentUpdater; workInProgress.stateNode = instance; // ...省略部分代码 }

这里给实例对象挂载updater属性,对应的值是classComponentUpdater,这个对象就是我们在开篇说Component时setState方法的具体实现了。也就是关子1⃣️的答案。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberClassComponent.old.js 中 const classComponentUpdater = { isMounted, enqueueSetState(inst, payload, callback) { const fiber = getInstance(inst); const eventTime = requestEventTime(); const lane = requestUpdateLane(fiber); // 创建更新 const update = createUpdate(eventTime, lane); update.payload = payload; if (callback !== undefined && callback !== null) { if (__DEV__) { warnOnInvalidCallback(callback, 'setState'); } update.callback = callback; } // 构建更新的链表 enqueueUpdate(fiber, update); // 进入更新逻辑,回归beginWork scheduleUpdateOnFiber(fiber, lane, eventTime); // ...省略部分代码 }, enqueueReplaceState(inst, payload, callback) { // ...省略代码 }, enqueueForceUpdate(inst, callback) { // ...省略代码 }, };

当我们在类组件里执行this.setState时,便会执行enqueueSetState方法。

enqueueSetState先创建update对象,然后构建updateQueue链表,最后执行scheduleUpdateOnFiber方法,回归到beginWork的更新状态。

我们接着看finishClassComponent方法吧。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberBeginWork.old.js 中 function finishClassComponent( current: Fiber | null, workInProgress: Fiber, Component: any, shouldUpdate: boolean, hasContext: boolean, renderLanes: Lanes, ) { // ...省略部分代码 const instance = workInProgress.stateNode; ReactCurrentOwner.current = workInProgress; let nextChildren; if ( didCaptureError && typeof Component.getDerivedStateFromError !== 'function' ) { // ...省略部分代码 } else { if (__DEV__) { // ...省略部分代码 } else { // 调用实例对象的render方法,获取React Element对象 nextChildren = instance.render(); } } workInProgress.flags |= PerformedWork; if (current !== null && didCaptureError) { // ...省略部分代码 } else { // 进入调和阶段,这是Reconciler的核心模块 reconcileChildren(current, workInProgress, nextChildren, renderLanes); } workInProgress.memoizedState = instance.state; // ...省略部分代码 return workInProgress.child; }

重点关注instance.render方法即我们书写在类组件里的render方法,它返回的是jsx。我们都知道React会把jsx转化为React Element对象。nextChildren就是转化后的React Element对象,reconcileChildren就是对这些对象进行调和的。

经过reconcileChildren后,便会赋值workInProgress的child对象。

reconcileChildren

函数reconcileChildren在文件 packages/react-reconciler/src/ReactFiberBeginWork.old.js 中,它同样根据current是否为null区分是mount还是update。如果是mount调用mountChildFibers方法,否则调用reconcileChildFibers方法。这里我们只看mountChildFibers方法。

// 本段源码在文件 packages/react-reconciler/src/ReactChildFiber.old.js 中 function reconcileChildFibers( returnFiber: Fiber, currentFirstChild: Fiber | null, newChild: any, lanes: Lanes, ): Fiber | null { // ...省略部分代码 // Handle object types const isObject = typeof newChild === 'object' && newChild !== null; if (isObject) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: return placeSingleChild( reconcileSingleElement( returnFiber, currentFirstChild, newChild, lanes, ), ); case REACT_PORTAL_TYPE: return placeSingleChild( reconcileSinglePortal( returnFiber, currentFirstChild, newChild, lanes, ), ); case REACT_LAZY_TYPE: if (enableLazyElements) { const payload = newChild._payload; const init = newChild._init; // TODO: This function is supposed to be non-recursive. return reconcileChildFibers( returnFiber, currentFirstChild, init(payload), lanes, ); } } } if (typeof newChild === 'string' || typeof newChild === 'number') { return placeSingleChild( reconcileSingleTextNode( returnFiber, currentFirstChild, '' + newChild, lanes, ), ); } if (isArray(newChild)) { return reconcileChildrenArray( returnFiber, currentFirstChild, newChild, lanes, ); } // ...省略部分代码 return deleteRemainingChildren(returnFiber, currentFirstChild); } return reconcileChildFibers; }

可以看到它根据不同类型,调用不同方法调和节点。我们先看调和单个节点的实现即reconcileSingleElement方法

// 本段源码在文件 packages/react-reconciler/src/ReactChildFiber.old.js 中 function reconcileSingleElement( returnFiber: Fiber, currentFirstChild: Fiber | null, element: ReactElement, lanes: Lanes, ): Fiber { const key = element.key; let child = currentFirstChild; while (child !== null) { // 这里是更新时执行 // ...省略部分代码 } if (element.type === REACT_FRAGMENT_TYPE) { const created = createFiberFromFragment( element.props.children, returnFiber.mode, lanes, element.key, ); created.return = returnFiber; return created; } else { const created = createFiberFromElement(element, returnFiber.mode, lanes); created.ref = coerceRef(returnFiber, currentFirstChild, element); created.return = returnFiber; return created; } }

在mount时到这,就根据element.type类型创建当前的fiber节点即createFiberFromElement方法和createFiberFromFragment。并赋值新节点的return指针。

我们再看看多节点的调和即reconcileChildrenArray方法

// 本段源码在文件 packages/react-reconciler/src/ReactChildFiber.old.js 中 function reconcileChildrenArray( returnFiber: Fiber, currentFirstChild: Fiber | null, newChildren: Array, lanes: Lanes, ): Fiber | null { // ...省略部分代码 let resultingFirstChild: Fiber | null = null; let previousNewFiber: Fiber | null = null; // mount时oldFiber为null let oldFiber = currentFirstChild; let lastPlacedIndex = 0; let newIdx = 0; let nextOldFiber = null; for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) { // ...更新是逻辑 } if (newIdx === newChildren.length) { deleteRemainingChildren(returnFiber, oldFiber); return resultingFirstChild; } if (oldFiber === null) { for (; newIdx < newChildren.length; newIdx++) { // 创建子节点 const newFiber = createChild(returnFiber, newChildren[newIdx], lanes); if (newFiber === null) { continue; } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) { // 保存第一个子节点 resultingFirstChild = newFiber; } else { // 把上一个子节点的sibling指向当前新创建的子节点 previousNewFiber.sibling = newFiber; } // 保存当前子节点为上一个子节点 previousNewFiber = newFiber; } // 返回第一个子节点 return resultingFirstChild; } // ...省略更新是逻辑 return resultingFirstChild; }

在mount时,从数组第一项开始遍历,创建第一个子节点时保存在resultingFirstChild上。

接着如果previousNewFiber为null也就是创建第一个子节点,把第一个子节点赋值给它,之后创建第二子节点时就可以执行previousNewFiber.sibling=newFiber即把第二子节点赋值给它的sibling指针,然后在把它指向第二子节点,这样第一个子节点的sibling就指向了第二个子节点。逐渐遍历下去,就有第二个子节点的sibling指向第三个子节点,第三个子节点的sibling指向第四个子节点。最后返回resultingFirstChild即第一个子节点。

返回的第一个子节点会在reconcileChildren方法中赋值给workInProgress.child。

这就是构建fiber树的具体过程。可以回头看看上面的Fiber树的图。

当然这里省略了更新时的调和也就是我们说的DOM Diff。因为本篇重点在mount的过程,所以这里不做解析。

在performUnitOfWork方法中,如果beinWork返回的结果为null就进入completeUnitOfWork方法处理当前的fiber节点。

而beginWork返回的是Fiber树的第一个子节点,也就是当前的深度遍历到底了。

completeUnitOfWork

在beinWork逻辑里我们只看到了创建Fiber树的过程,并没有看到如何创建真实DOM。

而completeUnitOfWork就是创建真实DOM的过程,并会遍历fiber树创建DOM树。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中 function completeUnitOfWork(unitOfWork: Fiber): void { let completedWork = unitOfWork; do { // ...省略部分代码 if ((completedWork.flags & Incomplete) === NoFlags) { // ...省略部分代码 if ( !enableProfilerTimer || (completedWork.mode & ProfileMode) === NoMode ) { next = completeWork(current, completedWork, subtreeRenderLanes); } else { // ...省略部分代码 next = completeWork(current, completedWork, subtreeRenderLanes); // ...省略部分代码 } resetCurrentDebugFiberInDEV(); if (next !== null) { workInProgress = next; return; } // ...省略部分代码 const siblingFiber = completedWork.sibling; if (siblingFiber !== null) { workInProgress = siblingFiber; return; } completedWork = returnFiber; workInProgress = completedWork; } while (completedWork !== null); // ...省略部分代码 }

此方法会调用completeWork方法,它会根据fiber.tag调用不同的处理逻辑,然后返回结果。

// 本段源码在文件 packages/react-reconciler/src/ReactFiberCompleteWork.old.js 中 function completeWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ): Fiber | null { const newProps = workInProgress.pendingProps; switch (workInProgress.tag) { // ...省略部分类型的处理逻辑 case HostComponent: { popHostContext(workInProgress); const rootContainerInstance = getRootHostContainer(); const type = workInProgress.type; if (current !== null && workInProgress.stateNode != null) { // ...省略部分代码 } else { // ...省略部分代码 if (wasHydrated) { // ...省略部分代码 } else { // 创建真实DOM const instance = createInstance( type, newProps, rootContainerInstance, currentHostContext, workInProgress, ); // ...省略部分代码 // 添加子DOM到父DOM中 appendAllChildren(instance, workInProgress, false, false); workInProgress.stateNode = instance; if ( // 处理DOM的属性 finalizeInitialChildren( instance, type, newProps, rootContainerInstance, currentHostContext, ) ) { markUpdate(workInProgress); } } // ...省略部分代码 } // ...省略部分代码 return null; } case HostText: { // ...省略部分代码 return null; } // ...省略类型处理逻辑 } // ...省略部分代码 }

如果completeWork方法返回null就会判断有没有sibling,如果有则返回,再次进入beginWork逻辑;如果没有就会把它的return属性赋值给当前的workInProgress就进入到父节点的completeWork过程,从而创建父节点对应的真实DOM并添加子DOM。方法中的createInstance就是创建真实DOM,appendAllChildren方法是添加子DOM的过程。

方法createInstance在文件packages/react-dom/src/client/ReactDOMHostConfig.js中。

方法appendAllChildren在文件packages/react-reconciler/src/ReactFiberCompleteWork.old.js中。

大家可以自己去看看。

我们以下面为例,一步步解析

function App() { reutrn ( 闹闹前端 javascript nodejs ) }

第一步进入div的beginWork,它的子节点是Array有p和ul,所以会构建div对应的fiber节点(便于说明我们叫它Fiber_Div )的child是p(Fiber_p),Fiber_p的sibling是ul(Fiber_ul)然后返回Fiber_Div的child作为下一个工作单元。

第二步即为Fiber_p进入beginWork,会调用updateHostComponent方法,调和它的子节点即文本节点闹闹前端,返回null。

第三步进入completeUnitOfWork方法,unitOfWork即为Fiber_p。然后调用completeWork方法,创建真实p DOM,并返回null。经过一些处理后,获取它的sibling,如果它有sibling就直接返回,在performUnitOfWork中重新赋值给next,再返回next。再进入beginWork阶段,这时就是调和Fiber_ul了。

第四步对Fiber_ul进行第一步到第三步的深度遍历。包括它的子节点li。

第五步当进入第二个li的completeUninOfWork后,它的sibling是null。这时就会执行

completedWork = returnFiber; workInProgress = completedWork;

它的returnFiber就是Fiber_ul,此时completedWork不为null,即进入while的第二次循环。

第六步进行Fiber_ul的completeWork,执行创建ul的真实DOM,并执行appendAllChildren方法,遍历它的child指针添加到真实的DOM中,从而构建真实的ul和li的DOM关系树。

第七步对Fiber_div执行同样的逻辑,创建真实的div DOM和以及它和p、ul的DOM关系树。

至此渲染阶段及render阶段结束。下面进入commit阶段。

commit阶段

方法commitRoot是commit阶段的入口。它执行commitRootImpl方法。

方法commitRootImpl的工作主要分为三部分:

执行commitBeforeMutationEffects方法即执行DOM操作前

执行commitMutationEffects方法即执行DOM操作,把DOM渲染到页面

执行commitLayoutEffects方法即执行DOM操作后

// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中 function commitRootImpl(root, renderPriorityLevel) { // ...省略部分代码 const finishedWork = root.finishedWork; const lanes = root.finishedLanes; // ...省略部分代码 root.finishedWork = null; root.finishedLanes = NoLanes; // ...省略部分代码 if (firstEffect !== null) { // ...省略部分代码 nextEffect = firstEffect; do { if (__DEV__) { // ...省略部分代码 } else { try { commitBeforeMutationEffects(); } catch (error) { // ...省略部分代码 } } } while (nextEffect !== null); // ...省略部分代码 nextEffect = firstEffect; do { if (__DEV__) { // ...省略部分代码 } else { try { commitMutationEffects(root, renderPriorityLevel); } catch (error) { // ...省略部分代码 } } } while (nextEffect !== null); // ...省略部分代码 nextEffect = firstEffect; do { if (__DEV__) { // ...省略部分代码 } else { try { commitLayoutEffects(root, lanes); } catch (error) { // ...省略部分代码 } } } while (nextEffect !== null); nextEffect = null; // ...省略部分代码 } else { // ...省略部分代码 } // ...省略部分代码 return null; } commitBeforeMutationEffects // 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中 function commitBeforeMutationEffects() { while (nextEffect !== null) { const current = nextEffect.alternate; if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) { // ... focus blur逻辑 } const flags = nextEffect.flags; if ((flags & Snapshot) !== NoFlags) { setCurrentDebugFiberInDEV(nextEffect); // 类组件调用getSnapshotBeforeUpdate生命周期方法,HostRoot类型清空容器 commitBeforeMutationEffectOnFiber(current, nextEffect); resetCurrentDebugFiberInDEV(); } // 调度useEffect if ((flags & Passive) !== NoFlags) { // If there are passive effects, schedule a callback to flush at // the earliest opportunity. if (!rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = true; scheduleCallback(NormalSchedulerPriority, () => { flushPassiveEffects(); return null; }); } } nextEffect = nextEffect.nextEffect; } }

整体功能分为三部分

处理DOM的focus和blur逻辑 调用类组件的getSnapshotBeforeUpdate生命周期方法 调度useEffect commitMutationEffects // 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中 function commitMutationEffects(root: FiberRoot, renderPriorityLevel) { // 遍历effect链表 while (nextEffect !== null) { setCurrentDebugFiberInDEV(nextEffect); const flags = nextEffect.flags; if (flags & ContentReset) { commitResetTextContent(nextEffect); } // 更新ref if (flags & Ref) { const current = nextEffect.alternate; if (current !== null) { commitDetachRef(current); } if (enableScopeAPI) { if (nextEffect.tag === ScopeComponent) { commitAttachRef(nextEffect); } } } // 根据effectTag不同,执行不同的逻辑处理 const primaryFlags = flags & (Placement | Update | Deletion | Hydrating); outer: switch (primaryFlags) { // 插入DOM case Placement: { commitPlacement(nextEffect); nextEffect.flags &= ~Placement; break; } // 插入DOM并更新DOM case PlacementAndUpdate: { // Placement commitPlacement(nextEffect); nextEffect.flags &= ~Placement; // Update const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // ...省略SSR逻辑 // 更新DOM case Update: { const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // 删除DOM case Deletion: { const deletedChild = nextEffect; const deletions = deletedChild.deletions; if (deletions !== null) { for (let i = 0; i < deletions.length; i++) { const deletion = deletions[i]; deletion.flags &= ~Deletion; deletion.deletions = null; commitDeletion(root, deletion, renderPriorityLevel); } } break; } } resetCurrentDebugFiberInDEV(); nextEffect = nextEffect.nextEffect; } }

此过程会根据effectTag,对每个Fiber节点进行增删改的操作。

方法commitPlacement 是插入DOM

// 本段源码在文件 packages/react-reconciler/src/ReactFiberCommitWork.old.js 中 function commitPlacement(finishedWork: Fiber): void { if (!supportsMutation) { return; } const parentFiber = getHostParentFiber(finishedWork); let parent; let isContainer; // 获取父级DOM节点 const parentStateNode = parentFiber.stateNode; switch (parentFiber.tag) { case HostComponent: parent = parentStateNode; isContainer = false; break; case HostRoot: parent = parentStateNode.containerInfo; isContainer = true; break; case HostPortal: parent = parentStateNode.containerInfo; isContainer = true; break; case FundamentalComponent: if (enableFundamentalAPI) { parent = parentStateNode.instance; isContainer = false; } default: // ...省略部分代码 } if (parentFiber.flags & ContentReset) { resetTextContent(parent); parentFiber.flags &= ~ContentReset; } // 获取兄弟DOM节点 const before = getHostSibling(finishedWork); // 根据情况决定是调用insertBefore还是appendChild方法执行DOM操作 if (isContainer) { insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent); } else { insertOrAppendPlacementNode(finishedWork, before, parent); } }

主要功能:

获取父级DOM节点 获取兄弟节点 根据情况决定是调用insertBefore还是appendChild方法执行DOM操作

在mount时,也是这在一步把Fiber上对应的DOM树渲染到root容器中的。

其他的更新commitWork和删除commitDeletion操作都是update时会执行的了,在这里就暂不讨论。

commitLayoutEffects // 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中 function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) { // ...省略部分代码 while (nextEffect !== null) { setCurrentDebugFiberInDEV(nextEffect); const flags = nextEffect.flags; if (flags & (Update | Callback)) { const current = nextEffect.alternate; // 调用生命周期componentDidMount和componentDidUpdate和hook commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes); } if (enableScopeAPI) { if (flags & Ref && nextEffect.tag !== ScopeComponent) { // 赋值ref commitAttachRef(nextEffect); } } else { if (flags & Ref) { // 赋值ref commitAttachRef(nextEffect); } } // ...省略部分代码 nextEffect = nextEffect.nextEffect; } // ...省略部分代码 }

此方法主要功能如下:

调用生命周期方法和hook有关的操作 赋值ref 总结

本篇从React的Top Level API的实现,再到ReactDOM.render方法的执行流程,接着到渲染阶段创建Fiber树及构建DOM树的过程,最后到commit阶段把真实DOM渲染到页面的过程,讲述了应用第一次加载的流程。

下面用一张图总结一下整个流程:

react17.01 (1).png

图太模糊,拆分为一下三张图:

react17.01.png

react17.01 (1).png

react17.01 (2).png 这样我们就系统的了解了React内部的一些架构设计和实现逻辑。过程还是比较复杂的,但是这也只是React的冰山一角。剩余的分支还有很多,比如React是如何更新状态及把状态映射到DOM上的?DOMDiff的具体实现及算法是什么?Hooks的实现及执行时机是什么?还有最重要的任务调度和异步渲染是如何实现的?都值得详细去探讨。

更多内容可以关注微信公众号:闹闹前端



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3