对ReactDOM.render执行后mount的流程简单梳理一下,重点分析一下mount时Fiber节点的操作,主要是循环调用beginWork和completeWork,以下的内容皆建立在mount时的基础上,React版本为17.0.1。
在执行render后,会进入一个比较深的调用栈来创建FiberRootNode。

接着会触发updateContainer函数,其中调用scheduleUpdateOnFiber发起更新调度,又会进入一个比较深的调用栈来构建Fiber树。

Fiber树
最多同时会有2棵Fiber树,一棵为current,是当前页面呈现的节点所对应的Fiber树,一棵为workInProgressRoot,是正在更新的Fiber树
两个Fiber节点之间通过alternate属性来连接,通过当前是否存在current节点来判断当前是mount阶段还是update阶段。
render执行时,首先会通过createFiberRoot函数创建FiberRootNode,同时会通过createHostRootFiber创建rootFiber并且挂在FiberRootNode的current属性上,后续mount时首先处理的就是rootFiber
Fiber树的构建顺序
Fiber树的构建是深度优先,先向下一直构建子节点(child),当没有子节点的时候尝试构建当前节点的兄弟节点(sibling),兄弟节点也没有时候返回父级节点(return)

renderRootSync
在最上面的调用栈图片中可以看到renderRootSync函数为workLoopSync的上层函数,这里会调用prepareFreshStack函数初始化workInProgressRoot和workInProgress,并赋值一些全局变量
workInProgressRoot = root;
workInProgress = createWorkInProgress(root.current, null); // 返回一个fiber节点
// 省略...workLoopSync
用ReactDOM.render调用的workLoopSync函数是同步的,会一直调用performUnitOfWork来构建一棵完整的fiber树,这个阶段被称为render阶段
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}performUnitOfWork
performUnitOfWork函数处理每一个Fiber节点,其中有两大阶段,一个是beginWork,一个是completeWork,beginWork会返回当前workInProgress节点的child作为下一个待处理的节点,completeWork会将workInProgress指向兄弟(sibling)或父级(return)节点
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;
let next;
// 省略...
// beginWork会返回下一个待处理的fiber节点
next = beginWork(current, unitOfWork, subtreeRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork); // completeWork的上层函数
} else {
workInProgress = next; // 还有child
}
// 省略...
}向child节点执行是由beginWork处理,向sibling和return节点执行是由completeWork处理,依照上图的例子,执行过程如下:
div beginWork
p beginWork
span beginWork
span completeWork
p completeWork
p beginWork (第2个p标签)
p completeWork (第2个p标签)
div completeWorkbeginWork
beginWork 函数在mount时会根据对应的tag来创建fiber节点
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// 通过current判断是否是update
if (current !== null)
// 省略update时...
// mount时
switch (workInProgress.tag) {
// 未确定类型组件
// 在mount时实际函数组件会在这个case
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderLanes,
);
}
// 省略其他类型...
case HostRoot:
// fiberRootNode都current节点进入updateHostRoot
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
// 原生标签
return updateHostComponent(current, workInProgress, renderLanes);
// 省略其他类型...
}
}performUnitOfWork第一次执行的Fiber节点为rootFiber,该fiber节点的tag为HostRoot,会进入updateHostRoot函数
updateHostRoot
updateHostRoot函数只处理rootFiber,主要执行了processUpdateQueue来执行一个更新队列,执行reconcileChildren来为workInProgress产生一个child fiber节点
为什么这里会执行一个更新队列呢?我目前也没搞明白,猜测可能与React DevTools还有ssr相关。
这个更新队列会产生一个类型为ReactElement的变量,传递给reconcileChildren调用来产生子fiber节点
function updateHostRoot(current, workInProgress, renderLanes) {
// 省略一些代码...
const nextProps = workInProgress.pendingProps;
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
const nextState = workInProgress.memoizedState;
const nextChildren = nextState.element; // ReactElement对象,也就是child
// 省略很多代码...
reconcileChildren(current, workInProgress, nextChildren, renderLanes); // 产生子fiber节点
return workInProgress.child;
}reconcileChildren
reconcileChildren函数在mount时会创建子fiber节点
function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
// mount
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
// 省略update...
}
}mountChildFibers是由高阶函数ChildReconciler产生的,其中主要实现reconcileChildFibers方法,该方法会判断传入的children是单个元素、多个元素、数组、还是字符串数字等等类型
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
const isObject = typeof newChild === 'object' && newChild !== null;
// 多个children的时候也是object只是不匹配$$typeof会匹配后面的isArray
if (isObject) {
switch (newChild.$$typeof) {
// 单个元素
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
// 省略很多代码...
}
}
// 字符串数字
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
lanes,
),
);
}
// 数组
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
}
// 省略很多代码...
}在mount时,currentFirstChild参数固定为null,如果是上面图片的结构,这里的newChild就是为div的ReactElement,则会进入单一元素的判断,返回reconcileSingleElement的执行结果
reconcileSingleElement
reconcileSingleElement中会调用createFiberFromElement来根据element类型创建fiber节点
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes,
): Fiber {
const key = element.key;
let child = currentFirstChild;
while (child !== null) {
// 复用的判断,mount时child固定为null不会走这里
}
// 省略Fragment...
const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
}createFiberFromElement方法最终会执行createFiberFromTypeAndProps方法
function createFiberFromElement(
element: ReactElement,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
let owner = null;
const type = element.type;
const key = element.key;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
lanes,
);
return fiber;
}createFiberFromTypeAndProps
createFiberFromTypeAndProps方法里会判断非常多的类型,这里只保留函数组件、类组件、原生标签的判断
function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
let fiberTag = IndeterminateComponent;
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
let resolvedType = type;
if (typeof type === 'function') {
// 通过prototype.isReactComponent判断是不是类组件
if (shouldConstruct(type)) {
fiberTag = ClassComponent;
}
} else if (typeof type === 'string') {
fiberTag = HostComponent;
}
// 省略很多代码..
// 如Fragment、Suspense的判断
const fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.lanes = lanes;
return fiber;
}这里有一个需要注意的地方,就是fiberTag默认为IndeterminateComponent,type为类组件或者是原生标签的时候才会改变tag,也就是说函数组件最后创建的fiber节点的tag为IndeterminateComponent,这会使函数组件的fiber在mount时走的是mountIndeterminateComponent方法
如上面的图结构的jsx这里创建的fiber节点是div,会把此fiber节点赋值给workInProgress.child,在下一次beginWork执行此fiber节点时,会进入updateHostComponent方法
updateHostComponent
在React里会走到updateHostComponent的fiber节点肯定是多的莫法
function updateHostComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
// 省略...
const type = workInProgress.type;
const nextProps = workInProgress.pendingProps;
const prevProps = current !== null ? current.memoizedProps : null;
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps); // 判断children是不是字符串、数字、InnerHTML等
if (isDirectTextChild) {
nextChildren = null; // 这是react对文字节点对优化,可以少创建一个text类型对fiber节点
}
// 省略...
markRef(current, workInProgress); // 标记是否有ref
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}在updateHostComponent方法里同样会调用之前提到的reconcileChildren方法来生成子fiber节点,在updateHostComponent对文本节点有一个优化对操作,少创建一个节点。
具体来讲就是type为textarea、option、noscript和children为string或number,或者有dangerouslySetInnerHTML的fiber节点。
completeWork
当深度遍历子节点完毕以后,会执行completeWork创建dom元素,同时在上层函数completeUnitOfWork中将workInProgress指向兄弟(sibling)或父级(return)节点
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// 省略...
// 创建dom实例
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// append child
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
// 设置props的属性如style,文本节点的children这个时候也会赋值
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
// 当有autoFocus当时候才需要打上update当flag
markUpdate(workInProgress);
}
// 省略...
}当completeWork最后返回到rootFiber了以后,会在上层函数performSyncWorkOnRoot中执行commitRoot方法,到这里render阶段结束。
commitRoot
commitRoot标志着render阶段结束,进入commit阶段,在commit阶段里,会触发生命周期和useLayoutEffect、useEffect钩子,同时会生成fiber节点所对应的dom节点
function commitRoot(root) {
const renderPriorityLevel = getCurrentPriorityLevel();
runWithPriority(
ImmediateSchedulerPriority,
commitRootImpl.bind(null, root, renderPriorityLevel),
);
return null;
}commitRoot实际是调用了scheduler调度器包里的方法来执行commitRootImpl
commitRootImpl
commitRootImpl方法里就是commit阶段的代码了,其中又分为3个子阶段
- before mutation阶段 操作
dom前 - mutation阶段 操作dom
- layout阶段 操作dom后
到这里的时候fiber树已经构建完成了