render阶段的入口
render阶段的次要工作是构建Fiber树和生成effectList,在第5章中咱们晓得了react入口的两种模式会进入performSyncWorkOnRoot或者performConcurrentWorkOnRoot,而这两个办法别离会调用workLoopSync或者workLoopConcurrent
//ReactFiberWorkLoop.old.jsfunction workLoopSync() { while (workInProgress !== null) { performUnitOfWork(workInProgress); }}function workLoopConcurrent() { while (workInProgress !== null && !shouldYield()) { performUnitOfWork(workInProgress); }}
这两函数的区别是判断条件是否存在shouldYield的执行,如果浏览器没有足够的工夫,那么会终止while循环,也不会执行前面的performUnitOfWork函数,天然也不会执行前面的render阶段和commit阶段,这部分属于scheduler的知识点,咱们在第15章解说。
- workInProgress:新创建的workInProgress fiber
- performUnitOfWork:workInProgress fiber和会和曾经创立的Fiber连接起来造成Fiber树。这个过程相似深度优先遍历,咱们暂且称它们为‘捕捉阶段’和‘冒泡阶段’。伪代码执行的过程大略如下
function performUnitOfWork(fiber) { if (fiber.child) { performUnitOfWork(fiber.child);//beginWork } if (fiber.sibling) { performUnitOfWork(fiber.sibling);//completeWork }}
render阶段整体执行流程
用demo_0看视频调试
- 捕捉阶段
从根节点rootFiber开始,遍历到叶子节点,每次遍历到的节点都会执行beginWork,并且传入以后Fiber节点,而后创立或复用它的子Fiber节点,并赋值给workInProgress.child。 - 冒泡阶段
在捕捉阶段遍历到子节点之后,会执行completeWork办法,执行实现之后会判断此节点的兄弟节点存不存在,如果存在就会为兄弟节点执行completeWork,当全副兄弟节点执行完之后,会向上‘冒泡’到父节点执行completeWork,直到rootFiber。 - 示例,demo_0调试
function App() { return ( <> <h1> <p>count</p> xiaochen </h1> </> )}ReactDOM.render(<App />, document.getElementById("root"));
当执行完深度优先遍历之后造成的Fiber树:
图中的数字是遍历过程中的程序,能够看到,遍历的过程中会从利用的根节点rootFiber开始,顺次执行beginWork和completeWork,最初造成一颗Fiber树,每个节点以child和return相连。
留神:当遍历到只有一个子文本节点的Fiber时,该Fiber节点的子节点不会执行beginWork和completeWork,如图中的‘chen’文本节点。这是react的一种优化伎俩
beginWork
beginWork次要的工作是创立或复用子fiber节点
function beginWork( current: Fiber | null,//以后存在于dom树中对应的Fiber树 workInProgress: Fiber,//正在构建的Fiber树 renderLanes: Lanes,//第12章在讲): Fiber | null { // 1.update时满足条件即可复用current fiber进入bailoutOnAlreadyFinishedWork函数 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; } //2.依据tag来创立不同的fiber 最初进入reconcileChildren函数 switch (workInProgress.tag) { case IndeterminateComponent: // ... case LazyComponent: // ... case FunctionComponent: // ... case ClassComponent: // ... case HostRoot: // ... case HostComponent: // ... case HostText: // ... }}
相干参考视频解说:进入学习
从代码中能够看到参数中有current Fiber,也就是以后实在dom对应的Fiber树,在之前介绍Fiber双缓存机制中,咱们晓得在首次渲染时除了rootFiber外,current 等于 null,因为首次渲染dom还没构建进去,在update时current不等于 null,因为update时dom树曾经存在了,所以beginWork函数中用current === null来判断是mount还是update进入不同的逻辑
- mount:依据fiber.tag进入不同fiber的创立函数,最初都会调用到reconcileChildren创立子Fiber
update:在构建workInProgress的时候,当满足条件时,会复用current Fiber来进行优化,也就是进入bailoutOnAlreadyFinishedWork的逻辑,能复用didReceiveUpdate变量是false,复用的条件是
- oldProps === newProps && workInProgress.type === current.type 属性和fiber的type不变
- !includesSomeLane(renderLanes, updateLanes) 更新的优先级是否足够,第15章解说
reconcileChildren/mountChildFibers
创立子fiber的过程会进入reconcileChildren,该函数的作用是为workInProgress fiber节点生成它的child fiber即 workInProgress.child。而后持续深度优先遍历它的子节点执行雷同的操作。
//ReactFiberBeginWork.old.jsexport function reconcileChildren( current: Fiber | null, workInProgress: Fiber, nextChildren: any, renderLanes: Lanes) { if (current === null) { //mount时 workInProgress.child = mountChildFibers( workInProgress, null, nextChildren, renderLanes, ); } else { //update workInProgress.child = reconcileChildFibers( workInProgress, current.child, nextChildren, renderLanes, ); }}
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
var reconcileChildFibers = ChildReconciler(true);var mountChildFibers = ChildReconciler(false);
function ChildReconciler(shouldTrackSideEffects) {function placeChild(newFiber, lastPlacedIndex, newIndex) { newFiber.index = newIndex; if (!shouldTrackSideEffects) {//是否追踪副作用 // Noop. return lastPlacedIndex; } var current = newFiber.alternate; if (current !== null) { var oldIndex = current.index; if (oldIndex < lastPlacedIndex) { // This is a move. newFiber.flags = Placement; return lastPlacedIndex; } else { // This item can stay in place. return oldIndex; } } else { // This is an insertion. newFiber.flags = Placement; return lastPlacedIndex; } }}
在之前心智模型的介绍中,咱们晓得为Fiber打上effectTag之后在commit阶段会被执行对应dom的增删改,而且在reconcileChildren的时候,rootFiber是存在alternate的,即rootFiber存在对应的current Fiber,所以rootFiber会走reconcileChildFibers的逻辑,所以shouldTrackSideEffects等于true会追踪副作用,最初为rootFiber打上Placement的effectTag,而后将dom一次性插入,进步性能。
export const NoFlags = /* */ 0b0000000000000000000;// 插入domexport const Placement = /* */ 0b00000000000010;
在源码的ReactFiberFlags.js文件中,用二进制位运算来判断是否存在Placement,例如让var a = NoFlags,如果须要在a上减少Placement的effectTag,就只有 effectTag | Placement就能够了
bailoutOnAlreadyFinishedWork
//ReactFiberBeginWork.old.jsfunction bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) { //... if (!includesSomeLane(renderLanes, workInProgress.childLanes)) { return null; } else { cloneChildFibers(current, workInProgress); return workInProgress.child; }}
如果进入了bailoutOnAlreadyFinishedWork复用的逻辑,会判断优先级第12章介绍,优先级足够则进入cloneChildFibers否则返回null
completeWork
completeWork次要工作是解决fiber的props、创立dom、创立effectList
//ReactFiberCompleteWork.old.jsfunction completeWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes,): Fiber | null { const newProps = workInProgress.pendingProps;//依据workInProgress.tag进入不同逻辑,这里咱们关注HostComponent,HostComponent,其余类型之后在讲 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) { // update时 updateHostComponent( current, workInProgress, type, newProps, rootContainerInstance, ); } else { // mount时 const currentHostContext = getHostContext(); // 创立fiber对应的dom节点 const instance = createInstance( type, newProps, rootContainerInstance, currentHostContext, workInProgress, ); // 将后辈dom节点插入刚创立的dom里 appendAllChildren(instance, workInProgress, false, false); // dom节点赋值给fiber.stateNode workInProgress.stateNode = instance; // 解决props和updateHostComponent相似 if ( finalizeInitialChildren( instance, type, newProps, rootContainerInstance, currentHostContext, ) ) { markUpdate(workInProgress); } } return null; }
从简化版的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函数中,例如咱们的利用是这样的
function App() { const [count, setCount] = useState(0); return ( <> <h1 onClick={() => { setCount(() => count + 1); }} > <p title={count}>{count}</p> xiaochen </h1> </> )}
那么咱们的操作effectList指针如下(这张图是操作指针过程中的图,此时遍历到了app Fiber节点,当遍历到rootFiber时,h1,p节点会和rootFiber造成环状链表)
rootFiber.firstEffect===h1rootFiber.firstEffect.next===p
造成环状链表的时候会从触发更新的节点向上合并effectList直到rootFiber,这一过程产生在completeUnitOfWork函数中,整个函数的作用就是向上合并effectList
//ReactFiberWorkLoop.old.jsfunction completeUnitOfWork(unitOfWork: Fiber): void { let completedWork = unitOfWork; do { //... if ( returnFiber !== null && (returnFiber.flags & Incomplete) === NoFlags ) { if (returnFiber.firstEffect === null) { returnFiber.firstEffect = completedWork.firstEffect;//父节点的effectList头指针指向completedWork的effectList头指针 } if (completedWork.lastEffect !== null) { if (returnFiber.lastEffect !== null) { //父节点的effectList头尾指针指向completedWork的effectList头指针 returnFiber.lastEffect.nextEffect = completedWork.firstEffect; } //父节拍板的effectList尾指针指向completedWork的effectList尾指针 returnFiber.lastEffect = completedWork.lastEffect; } const flags = completedWork.flags; if (flags > PerformedWork) { if (returnFiber.lastEffect !== null) { //completedWork自身追加到returnFiber的effectList结尾 returnFiber.lastEffect.nextEffect = completedWork; } else { //returnFiber的effectList头节点指向completedWork returnFiber.firstEffect = completedWork; } //returnFiber的effectList尾节点指向completedWork returnFiber.lastEffect = completedWork; } } } else { //... if (returnFiber !== null) { returnFiber.firstEffect = returnFiber.lastEffect = null;//重制effectList returnFiber.flags |= Incomplete; } } } while (completedWork !== null); //...}
最初生成的fiber树如下
而后commitRoot(root);进入commit阶段