render 阶段 render
阶段的主要工作是构建Fiber
树和生成effectList
,在第5章中我们知道了react入口的两种模式会进入performSyncWorkOnRoot
或者performConcurrentWorkOnRoot
,而这两个方法分别会调用workLoopSync
或者workLoopConcurrent
1 2 3 4 5 6 7 8 9 10 11 function workLoopSync ( ) { while (workInProgress !== null ) { performUnitOfWork(workInProgress); } } function workLoopConcurrent ( ) { while (workInProgress !== null && !shouldYield()) { performUnitOfWork(workInProgress); } }
这两函数的区别是判断条件是否存在shouldYield
的执行,如果浏览器没有足够的时间,那么会终止while
循环,也不会执行后面的performUnitOfWork
函数,自然也不会执行后面的render阶段和commit阶段,这部分属于scheduler
的知识点,我们在第12章讲解。
workInProgress:新创建的workInProgress fiber
performUnitOfWork:workInProgress fiber
和会和已经创建的Fiber
连接起来形成Fiber
树。这个过程类似深度优先遍历,我们暂且称它们为‘捕获阶段’和‘冒泡阶段’。执行的过程大概如下
1 2 3 4 5 6 7 8 9 function performUnitOfWork (fiber ) { if (fiber.child) { performUnitOfWork(fiber.child); } if (fiber.sibling) { performUnitOfWork(fiber.sibling); } }
render阶段整体执行流程 看断点调试视频,函数执行细节更清楚详细:
从根节点rootFiber
开始,遍历到叶子节点,每次遍历到的节点都会执行beginWork
,并且传入当前Fiber
节点,然后创建或复用它的子Fiber
节点,并赋值给workInProgress.child
。
在捕获阶段遍历到子节点之后,会执行completeWork
方法,执行完成之后会判断此节点的兄弟节点存不存在,如果存在就会为兄弟节点执行completeWork
,当全部兄弟节点执行完之后,会向上‘冒泡’到父节点执行completeWork
,直到rootFiber
。
示例
1 2 3 4 5 6 7 8 9 function App ( ) { return ( <div> xiao <p>chen</p> </div> ) } ReactDOM.render(<App /> , document .getElementById("root" ));
当执行完深度优先遍历之后形成的Fiber
树
图中的数字是遍历过程中的顺序,可以看到,遍历的过程中会从应用的根节点rootFiber
开始,依次执行beginWork
和completeWork
,最后形成一颗Fiber
树,每个节点以child
和return
相连。
注意:当遍历到只有一个子节点的Fiber
时,该Fiber
节点的子节点不会执行beginWork
和completeWork
,如图中的chen
文本节点。这是react
的一种优化手段
beginWork beginWork
主要的工作是创建或复用子fiber
节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 function beginWork ( current: Fiber | null , workInProgress: Fiber, renderLanes: Lanes, ): Fiber | null { if (current !== null ) { const oldProps = current.memoizedProps; const newProps = workInProgress.pendingProps; if ( oldProps !== newProps || hasLegacyContextChanged() || (__DEV__ ? workInProgress.type !== current.type : false ) ) { didReceiveUpdate = true ; } else if (!includesSomeLane(renderLanes, updateLanes)) { didReceiveUpdate = false ; switch (workInProgress.tag) { } return bailoutOnAlreadyFinishedWork( current, workInProgress, renderLanes, ); } else { didReceiveUpdate = false ; } } else { didReceiveUpdate = false ; } switch (workInProgress.tag) { case IndeterminateComponent: case LazyComponent: case FunctionComponent: case ClassComponent: case HostRoot: case HostComponent: return updateHostComponent(current, workInProgress, renderLanes) case HostText: } }
从代码中可以看到参数中有current Fiber
,也就是当前真实dom
对应的Fiber
树,在之前介绍Fiber
双缓存机制中,我们知道在首次渲染时除了rootFiber
外,current
等于 null
,因为首次渲染dom
还没构建出来,在update
时current
不等于 null
,因为update
时dom
树已经存在了,所以beginWork
函数中用current === null
来判断是mount
还是update
进入不同的逻辑
1 2 3 4 5 6 function updateHostComponent (current, workInProgress, renderLanes ) { var isDirectTextChild = shouldTextContent(type, nextProps) reconcileChildren(current, workInProgress, nextChildren, renderLanes) return workInProgress.child }
1 2 3 4 5 6 7 function reconcileChildren (current, workInProgress, nextChildren, renderLanes ) { if (current === null ) { workInProgress.child = mountChildFibers(workInProgress, null , nextChildren, renderLanes) } else { workInProgress.child = reconcileChildren(current, workInProgress, nextChildren, renderLanes) } }
创建子fiber
的过程会进入reconcileChildren
,该函数的作用是为workInProgress fiber
节点生成它的child fiber
即 workInProgress.child
。然后继续深度优先遍历它的子节点执行相同的操作。
reconcileChildren
会区分mount
和update
两种情况,进入reconcileChildFibers
或mountChildFibers
,reconcileChildFibers
和mountChildFibers
最终其实就是ChildReconciler
传递不同的参数返回的函数,这个参数用来表示是否追踪副作用,在ChildReconciler
中用shouldTrackSideEffects
来判断是否为对应的节点打上effectTag
,例如如果一个节点需要进行插入操作,需要满足两个条件:
fiber.stateNode!==null
即fiber
存在真实dom
,真实dom
保存在stateNode
上
(fiber.effectTag & Placement) !== 0
fiber
存在Placement
的effectTag
1 2 export const reconcileChildFibers = ChildReconciler(true )export const mountChildFibers = ChildReconciler(false )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function ChildReconciler (shouldTrackSideEffects ) { function placeChild (newFiber, lastPlacedIndex, newIndex ) { newFiber.index = newIndex; if (!shouldTrackSideEffects) { return lastPlacedIndex; } var current = newFiber.alternate; if (current !== null ) { var oldIndex = current.index; if (oldIndex < lastPlacedIndex) { newFiber.flags = Placement; return lastPlacedIndex; } else { return oldIndex; } } else { newFiber.flags = Placement; return lastPlacedIndex; } } }
在之前心智模型的介绍中,我们知道为Fiber
打上effectTag
之后在commit
阶段会被执行对应dom
的增删改,而且在reconcileChildren
的时候,rootFiber
是存在alternate
的,即rootFiber
存在对应的current Fiber
,所以rootFiber
会走reconcileChildFibers
的逻辑,所以shouldTrackSideEffects
等于true
会追踪副作用,最后为rootFiber
打上Placement
的effectTag
,然后将dom
一次性插入,提高性能。
1 2 3 export const NoFlags = 0b0000000000000000000 ;export const Placement = 0b00000000000010 ;
在源码的ReactFiberFlags.js
文件中,用二进制位运算来判断是否存在Placement
,例如让var a = NoFlags
,如果需要在a
上增加Placement
的effectTag
,就只要effectTag | Placement
就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 function reconcileChildFibers (returnFiber, currentFirstChild, newChild, lanes ) { var isObject = typeof newChild === 'object' && newChild !== null switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes)) } } function reconcileSingleElement (returnFiber, currentFirstChild, element, lanes ) { var key = element.key var child = currentFirstChild while (child !== null ) { } if (element.type === REACT_ELEMENT_TYPE) { var created = createFiberFromElement(element.props.children, returnFiber.mode, lanes, element.key); created.return = returnFiber; return created; } else { var created = createFiberFromElement(element, returnFiber.mode, lanes) create } } function createFiberFromElement (element, mode, lanes ) { var fiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes) return fiber } function createFiberFromTypeAndProps (type, key, pendingProps, owner, mode, lanes ) { if (typeof type == 'function' ) { } else if (typeof type === 'string' ) { } var fiber = createFiber(fiberTag, pendingProps, key, mode) fiber.elementType = type fiber.type = resolvedType fiber.lanes = lanes return fiber }
beginWork update 进入到 beginWork
函数, 现在已存在current
, 进入update
逻辑, 看节点是否能复用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 function beginWork ( current: Fiber | null , workInProgress: Fiber, renderLanes: Lanes, ): Fiber | null { if (current !== null ) { const oldProps = current.memoizedProps; const newProps = workInProgress.pendingProps; if ( oldProps !== newProps || hasLegacyContextChanged() || (__DEV__ ? workInProgress.type !== current.type : false ) ) { didReceiveUpdate = true ; } else if (!includesSomeLane(renderLanes, updateLanes)) { didReceiveUpdate = false ; switch (workInProgress.tag) { } return bailoutOnAlreadyFinishedWork( current, workInProgress, renderLanes, ); } else { didReceiveUpdate = false ; } } else { didReceiveUpdate = false ; } switch (workInProgress.tag) { case IndeterminateComponent: case LazyComponent: case FunctionComponent: case ClassComponent: case HostRoot: case HostComponent: return updateHostComponent(current, workInProgress, renderLanes) case HostText: } }
节点能服用 1 2 3 4 5 6 7 8 function bailoutOnAlreadyFinishedWork (current, workInProgress, renderLanes ) { if (!includesSomeLane(renderLanes, workInProgress.childLanes)) { return null } else { cloneChildFibers(current, workInProgress) return workInProgress.child } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function cloneChildFibers (current, workInProgress ) { if (workInProgress.child === null ) { return ; } var currentChild = workInProgress.child var newChild = createWorkInProgress(currentChild, currentChild.pendingProps) workInProgress.child = newChild newChild.return = workInProgress while (currentChild.sibling !== null ) { currentChild = currentChild.sibling newChild = newChild.sibling = createWorkInProgress(currentChild, currentChild.pendingProps) newChild.return = workInProgress } newChild.sibling = null }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function createWorkInProgress (current, pendingProps ) { var workInProgress =current.alternate if (workInProgress === null ) { workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode) workInProgress.elementType = current.elementType workInProgress.type = current.type workInProgress.stateNode = current.stateNode } return workInProgress }
节点不能复用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 function beginWork ( ) { workInProgress.lanes = NoLanes switch (workInProgress.tag) { case HostComponent: return updateHostComponent(current, workInProgress, renderLanes) } } function updateHostComponent (current, workInProgress, renderLanes ) { reconcileChildren (current, workInProgress, nextChildren, renderLanes ) { if (current === null ) { workInProgress.child = mountChildFibers(workInProgress, null , nextChildren, renderLanes) } else { workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes) } } } function reconcileChildFibers (returnFiber, currentFirstChild, newChild, lanes ) { if (isObject) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes)) } } }
completeWork completeWork
主要工作是处理fiber
的props
、创建dom
、创建effectList
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 function completeWork ( current: Fiber | null , workInProgress: Fiber, renderLanes: Lanes, ): Fiber | null { const newProps = workInProgress.pendingProps; switch (workInProgress.tag) { case IndeterminateComponent: case LazyComponent: case SimpleMemoComponent: case HostRoot: case HostComponent: { popHostContext(workInProgress); const rootContainerInstance = getRootHostContainer(); const type = workInProgress.type; if (current !== null && workInProgress.stateNode != null ) { updateHostComponent( current, workInProgress, type, newProps, rootContainerInstance, ); } else { const currentHostContext = getHostContext(); const instance = createInstance( type, newProps, rootContainerInstance, currentHostContext, workInProgress, ); appendAllChildren(instance, workInProgress, false , false ); workInProgress.stateNode = instance; if ( finalizeInitialChildren( instance, type, newProps, rootContainerInstance, currentHostContext, ) ) { markUpdate(workInProgress); } } return null ; }
1 2 3 4 5 6 7 8 function createInstance (type, props, rootContainerInstance, hostContext, internalInstanceHandle ) { var domElement = createElement(type, props, rootContainerInstance, parentNamespace) precacheFiber(internalInstanceHandle, domElement) updateFiberNode(domElement, props) return domElement }
completeWork update 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function completeWork (current, workInProgress, renderLanes ) { switch (workInProgress.tag) { case HostComponent: if (current !== null && workInProgress.stateNode !== null ) { updateHostComponent$1 (current, workInProgress, type, newProps, rootContainerInstance) } } } function updateHostComponent$1 (current, workInProgress, type, newProps, rootContainerInstance ) { var updatePayload = prepareUpdate(instance, type, oldProps, rootContainerInstance, currentHostContext) workInProgress.updateQueue = updatePayload if (updatePayload) { markUpdate(workInProgress) } } function prepareUpdate (domElement, type, oldProps, newProps ) { return diffProperties(domElement, type, oldProps, newProps) } function markUpdate (workInProgress ) { workInProgress.flags |= Update }
从简化版的completeWork
中可以看到,这个函数做了一下几件事
根据workInProgress.tag
进入不同函数,我们以HostComponent
举例
update
时(除了判断current===null
外还需要判断workInProgress.stateNode===null
),调用updateHostComponent
处理props
(包括onClick、style、children
…),并将处理好的props
赋值给updatePayload
,最后会保存在workInProgress.updateQueue
上
mount
时 调用createInstance
创建dom
,将后代dom
节点插入刚创建的dom
中,调用finalizeInitialChildren
处理props
(和updateHostComponent
处理的逻辑类似)
之前我们有说到在beginWork
的mount
时,rootFiber
存在对应的current
,所以他会执行mountChildFibers
打上Placement
的effectTag
,在冒泡阶段也就是执行completeWork
时,我们将子孙节点通过appendAllChildren
挂载到新创建的dom
节点上,最后就可以一次性将内存中的节点用dom
原生方法反应到真实dom
中。
在beginWork
中我们知道有的节点被打上了effectTag
的标记,有的没有,而在commit
阶段时要遍历所有包含effectTag
的Fiber
来执行对应的增删改,那我们还需要从Fiber
树中找到这些带effectTag
的节点嘛,答案是不需要的,这里是以空间换时间,在执行completeWork
的时候遇到了带effectTag
的节点,会将这个节点加入一个叫effectList
中,所以在commit
阶段只要遍历effectList
就可以了(rootFiber.firstEffect.nextEffect
就可以访问带effectTag
的Fiber
了)
effectList
的指针操作发生在completeUnitOfWork
函数中,例如我们的应用是这样的
1 2 3 4 5 6 7 8 9 10 function App ( ) { const [count, setCount] = useState(0 ); return ( <div className="App" > <p onClick={() => setCount(() => count + 1 )}> <h1 title={count}>{count}</h1> and save to reload. </p> </div> ); }
那么我们的操作effectList
指针如下(这张图是操作指针过程中的图,此时遍历到了app Fiber
节点,当遍历到rootFiber
时,h1
,p
节点会和rootFiber
形成环状链表)
1 2 rootFiber.firstEffect===h1 rootFiber.firstEffect.next===p
最后生成的fiber
树如下
effectList 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function completeUnitOfWork (unitOfWork ) { if (returnFiber !== null && (returnFiber.flags & Incomplete) === NoFlags) { if (returnFiber.firstEffect === null ) { returnFiber.firstEffect = completedWork.firstEffect } if (completedWork.lastEffect !== null ) { if (returnFiber.lastEffect !== null ) { returnFiber.lastEffect = completedWork.firstEffect; } returnFiber.lastEffect = completedWork.lastEffect } var flags = completedWork.flags if (flags > PerformedWork) { if (returnFiber.lastEffect !== null ) { returnFiber.lastEffect.nextEffect = completedWork } else { returnFiber.firstEffect = completedWork } returnFiber.lastEffect = completedWork } } } var finishedWork = root.current.alternateroot.finishedWork = finishedWork root.finishedLanes = lanes commitRoot(root)