对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 beginWorkp beginWorkspan beginWorkspan completeWorkp completeWorkp 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树已经构建完成了