对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 completeWork
beginWork
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树已经构建完成了