佛山市做网站的免费字体设计图片
web/
2025/9/27 3:32:48/
文章来源:
佛山市做网站的,免费字体设计图片,wordpress 案例,广州注册公司名称completeUnitOfWork 1 #xff09;概述
各种不同类型组件的一个更新过程对应的是在执行 performUnitOfWork 里面的 beginWork 阶段它是去向下遍历一棵 fiber 树的一侧的子节点#xff0c;然后遍历到叶子节点为止#xff0c;以及 return 自己 child 的这种方式在 performUni…completeUnitOfWork 1 概述
各种不同类型组件的一个更新过程对应的是在执行 performUnitOfWork 里面的 beginWork 阶段它是去向下遍历一棵 fiber 树的一侧的子节点然后遍历到叶子节点为止以及 return 自己 child 的这种方式在 performUnitOfWork 里面还有一个方法叫做 completeUnitOfWork在 completeUnitOfWork 中会根据是否有中断调用不同的一个处理方法什么叫中断呢 就是我们在渲染一个组件的时候出现了错误的场景进行处理或者优先级被更高优先级打断低优先级转移到下一次处理 在 renderRoot 当中, 使用try catch去调用 workLoop 去循环每一个节点的workLoop 里面调用的就是 performUnitOfWork在 performUnitOfWork 里面调用了 beginWork它里面就是执行各种不同组件的一个update的一个过程在后面 completeUnitOfWork 是处理到一侧的子节点遍历到最下层的时候它没有子节点可以返回了因为 beginWork 里面调用了各种update它们都是return自己的child比如说有这么一棵 fiber 树的一个情况的时候 我们先去对 RootFiber 执行了 beginWork然后执行了之后 return 的是它的 child也就是 App一层一层这么执行下来之后到了有分叉节点也就是说一个节点下面有多个子节点的情况它 return 的还是它的第一个 child 节点所以这个时候后面的节点是没有被执行到更新的因为 inpput 它返回了之后返回的是它里面的 input child 这个节点到这里为止这个 dom 的 input 节点已经没有child了这个时候就 return null因为它的 child是 null所以在这里首先执行的是 beginWork如果有child它就会一直往child方向进行一个查找等到 next 等于 null 的情况的时候就会去执行 completeUnitOfWorkcompleteUnitOfWork 就是对当前这个input界定 执行它的complete具体执行什么在 beginWork 的过程当中如果我们去update某一个节点的时候有报错了或者是 throw 了一个 promise, 就是 suspend 的情况, 它会执行一定的标记在 renderRoot 当中我们可以看到它如果有报错它的catch是要在 renderRoot 里面被捕获的这个错误对这个错误补获之后它是有一定的处理的, 处理完之后它仍然处于这个 while 循环当中它里面并没有 break 位置: packages/react-reconciler/src/ReactFiberScheduler.js#L1276 这个时候它还是会往下进行一个执行的,只不过它会在里面给我们当前这个节点去执行一个标记在 completeUnitOfWork 的阶段会对有各种不同标记的一个节点执行不同的方法主要的是我们正常流程走的是叫 completeWork 这么一个方法而对于有异常的节点它的一个流程是调用一个叫 unwindWork 的方法然后会判断是否有兄弟节点来执行不同的操作 比如执行到上图 input 这个节点的 completeUnitOfWork之后它没有兄弟节点它直接去执行它的父节点也就是 Input 组件的 completeUnitOfWork在这里我们发现它是有兄弟节点的因为第一次update的过程是往下的对于旁边的 List 节点是没有任何更新的, 这个时候我们就会返回它的兄弟节点 List 对于这个兄弟节点, 继续对它执行一个update的一个过程也就是 beginWork 的一个过程这就是在 completeUnitOfWork 里面非常重要的一个判断条件 就是如果有兄弟节点它会直接返回这个兄弟节点 完成节点之后要赋值整个effect链 effect是非常重要的后面即将要把所有的更新结束的节点挂载到真正的dom上面这一个阶段叫做commit阶段它要去执行每一个节点不同的跟dom有关的操作在前面 beginWork 和 completeUnitOfWork对每一个节点的更新过程标记了有哪些 SideEffect 最终要被commit的一个过程在 completeUnitOfWork 里面会完成这一步把所有的effect节点进行一个串联让 commitWork 的阶段可以非常方便的根据这个链去执行每一个节点的最终的操作
2 源码
定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L939 找到 completeUnitOfWork
// 这个API 是在 workInProgress 的基础上进行的一个操作
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {// Attempt to complete the current unit of work, then move to the// next sibling. If there are no more siblings, return to the// parent fiber.// 进来首先就是一个 while true 的一个循环while (true) {// The current, flushed, state of this fiber is the alternate.// Ideally nothing should rely on this, but relying on it here// means that we dont need an additional field on the work in// progress.// 对于这个循环, 它首先获取currentconst current workInProgress.alternate;if (__DEV__) {ReactCurrentFiber.setCurrentFiber(workInProgress);}// 然后 获取 returnFiber 和 siblingFiber也就是它的父节点以及它的兄弟节点// 这个在后面我们判断是否要返回兄弟节点的时候就会用到const returnFiber workInProgress.return;const siblingFiber workInProgress.sibling;// 这里首先一进来就有一个大的判断它是一个整个方法里面最大的一个判断// Incomplete 就是这个节点它是出现了错误然后被捕获的, 并标记这个 sideEffect// 逻辑与操作来判断某一个属性上面它是否有某一个特性的一个方式if ((workInProgress.effectTag Incomplete) NoEffect) {if (__DEV__ replayFailedUnitOfWorkWithInvokeGuardedCallback) {// Dont replay if it fails during completion phase.mayReplayFailedUnitOfWork false;}// This fiber completed.// Remember were completing this unit so we can find a boundary if it fails.nextUnitOfWork workInProgress;if (enableProfilerTimer) {if (workInProgress.mode ProfileMode) {startProfilerTimer(workInProgress);}nextUnitOfWork completeWork(current,workInProgress,nextRenderExpirationTime,);if (workInProgress.mode ProfileMode) {// Update render duration assuming we didnt error.stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);}} else {nextUnitOfWork completeWork(current,workInProgress,nextRenderExpirationTime,);}if (__DEV__ replayFailedUnitOfWorkWithInvokeGuardedCallback) {// Were out of completion phase so replaying is fine now.mayReplayFailedUnitOfWork true;}stopWorkTimer(workInProgress);resetChildExpirationTime(workInProgress, nextRenderExpirationTime);if (__DEV__) {ReactCurrentFiber.resetCurrentFiber();}if (nextUnitOfWork ! null) {// Completing this fiber spawned new work. Work on that next.return nextUnitOfWork;}// 接下去这个个判断是要构造一个所有 有 SideEffect 的节点的一个链状的结构// 这个链状结构最终是用于 commitWork 的时候用来进行对这些有 SideEffect的节点进行 commit 的一个操作,// 这边它的一个判断条件returnFiber不等于null并且returnFiber它不是一个 Incomplete 的一个节点// 因为对于一个 Incomplete 的节点它唯一可以具有的一个SideEffect就是这个节点已经被捕获了// 因为对于有 Incomplete 错误的节点是不会渲染正常的子节点的if (returnFiber ! null // Do not append effects to parents if a sibling failed to complete(returnFiber.effectTag Incomplete) NoEffect) {// Append all the effects of the subtree and this fiber onto the effect// list of the parent. The completion order of the children affects the// side-effect order.// 对于正常的一个情况, 首先要判断一下 returnFiber.firstEffect 是否等于 null// 符合判断就代表 现在这个 returnFiber 上还没有记录任何它的子节点的有副作用的子节点// 这个时候, 直接把当前节点的firstEffect赋值给 returnFiber.firstEffect// 因为它之前是没有任何一个的嘛我们这边真正要做的是把当前节点的 firstEffect 到 lastEffect的一个链条// 这个单项链表给它挂载到它的父节点的同样的一个 firstEffect到lastEffect的单项链表的最后if (returnFiber.firstEffect null) {returnFiber.firstEffect workInProgress.firstEffect;}// 就是下面这一段判断就是来做这个事情的, 如果returnFiber.lastEffect不等于null那说明它已经有了// 那么对于returnFiber上面有记录过别的 SideEffect 的节点之后// 我们当前节点是挂载到整个 SideEffect 链的最后就是下面这样就是把它连到最后的上面if (workInProgress.lastEffect ! null) {if (returnFiber.lastEffect ! null) {returnFiber.lastEffect.nextEffect workInProgress.firstEffect;}// 还要操作如下因为它的这个(returnFiber.lastEffect)指针目前还指向它原来的那个lastEffect// 在 赋值 nextEffect之后它的最后一个就是这个链的最后一个已经变成 workInProgress.lastEffect所以这边要执行这么一个操作// 当然这个条件是要建立在 workInProgress.lastEffect 是有值的情况// 这是把它们各自的firsteffect到lasteffect这个链给它进行一个串联的过程returnFiber.lastEffect workInProgress.lastEffect;}// If this fiber had side-effects, we append it AFTER the childrens// side-effects. We can perform certain side-effects earlier if// needed, by doing multiple passes over the effect list. We dont want// to schedule our own side-effect on our own list because if end up// reusing children well schedule this effect onto itself since were// at the end.// 对于returnFiber来说当前这个节点也可能是有副作用的那么这边就接下去就会做这个操作// 如果当前节点的 effectTag PerformedWork 的因为 PerformedWork 是一个给 DEVTool 用的一个 sideEffect// 对于真正的react更新是没有任何意义的, 所以如果它仅仅只有 PerformedWork 它就不是一个有效的 SideEffect 的节点const effectTag workInProgress.effectTag;// Skip both NoWork and PerformedWork tags when creating the effect list.// PerformedWork effect is read by React DevTools but shouldnt be committed.if (effectTag PerformedWork) {// 如果它有 SideEffect就把当前节点作为父节点的 SideEffect 链的最后一个给它挂载上去// 或者如果是当前父节点没有任何记录的 SideEffect它就是第一个if (returnFiber.lastEffect ! null) {returnFiber.lastEffect.nextEffect workInProgress;} else {returnFiber.firstEffect workInProgress;}returnFiber.lastEffect workInProgress;}}if (__DEV__ ReactFiberInstrumentation.debugTool) {ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);}if (siblingFiber ! null) {// If there is more work to do in this returnFiber, do that next.return siblingFiber;} else if (returnFiber ! null) {// If theres no more work in this returnFiber. Complete the returnFiber.workInProgress returnFiber;continue;} else {// Weve reached the root.return null;}} else {if (enableProfilerTimer workInProgress.mode ProfileMode) {// Record the render duration for the fiber that errored.stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);// Include the time spent working on failed children before continuing.let actualDuration workInProgress.actualDuration;let child workInProgress.child;while (child ! null) {actualDuration child.actualDuration;child child.sibling;}workInProgress.actualDuration actualDuration;}// This fiber did not complete because something threw. Pop values off// the stack without entering the complete phase. If this is a boundary,// capture values if possible.const next unwindWork(workInProgress, nextRenderExpirationTime);// Because this fiber did not complete, dont reset its expiration time.if (workInProgress.effectTag DidCapture) {// Restarting an error boundarystopFailedWorkTimer(workInProgress);} else {stopWorkTimer(workInProgress);}if (__DEV__) {ReactCurrentFiber.resetCurrentFiber();}if (next ! null) {stopWorkTimer(workInProgress);if (__DEV__ ReactFiberInstrumentation.debugTool) {ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);}// If completing this work spawned new work, do that next. Well come// back here again.// Since were restarting, remove anything that is not a host effect// from the effect tag.next.effectTag HostEffectMask;return next;}if (returnFiber ! null) {// Mark the parent fiber as incomplete and clear its effect list.returnFiber.firstEffect returnFiber.lastEffect null;returnFiber.effectTag | Incomplete;}if (__DEV__ ReactFiberInstrumentation.debugTool) {ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);}// 存在 sibling 节点 注意这边是return也就是跳出while循环if (siblingFiber ! null) {// If there is more work to do in this returnFiber, do that next.return siblingFiber;} else if (returnFiber ! null) {// If theres no more work in this returnFiber. Complete the returnFiber.// 如果returnFiber不等于null那么 workInProgress returnFiber// 比如对于最上面示例图的 input节点它的 completeUnitOfWork// 如果它没有兄弟节点那么它就继续执行Input的 completeUnitOfWork// 这个循环是在 completeUnitOfWork 内部进行的一个过程workInProgress returnFiber;continue;} else {// 如果 returnFiber 也等于null那么它直接 return null// 说明已经到达顶点了就到达 RootFiber 了我们的更新过程呢已经完成了// 只需要在接下去 commitRoot 就可以了return null;}}}// Without this explicit null return Flow complains of invalid return type// TODO Remove the above while(true) loop// eslint-disable-next-line no-unreachablereturn null;
}对于这个while循环里面其实它的所有代码都是在if else里面的if else它们最大的一个区别是什么呢 在if里面我们可以看这边这个特殊情况就是 nextUnitOfWork 等于 completeWork然后传入一些属性对于 else 的情况这边调用了 next unwindWork这就是对于一个是否有抛出过错误的一个节点执行的一个不同的操作 completeWork是没有任何一个错误的一个节点它的一个complete的一个过程 调用了completework就是把这个节点的更新给它完成了 unwindWork 是有节点的错误被捕获如何去处理的一个过程在上面的 SideEffect 到底有什么意义呢比如有如下场景 有个列表上有值为 1, 2, 3的三个节点点击 button 按钮会让上面三个节点内的值乘上自身对于2,3是4和9对于1还是1这里 1 是一个没有变化的过程按照正常逻辑来说只需要更新原本值是 2 和 3 的节点实际上 react 里面也是这样做的 参考demo如下import React, { Component } from react
import ./App.cssclass List extends Component {state {a: 1,b: 2,c: 3,}handleClick () {this.setState(oldState {const { a, b, c } oldStatereturn {a: a * a,b: b * b,c: c * c,}})}render() {const { a, b, c } this.statereturn [span keya{a}/span,span keyb{b}/span,span keyc{c}/span,button keybutton onClick{this.handleClick}click me/button,]}
}class Input extends Component {state {name: wang,}handleChange e {// 这里如果使用方法设置state// 那么需要现在外面读取e.target.value// 因为在React走完整个事件之后会重置event对象// 以复用event对象如果等到方法被调用的时候再读取e.target.value// 那时e.target是nullthis.setState({name: e.target.value,})}render() {return (inputtypetextstyle{{ color: red }}onChange{this.handleChange}value{this.state.name}/)}
}class App extends Component {render() {return (div classNamemainInput /List //div)}
}export default App这个时候我们在 浏览器中针对 react-dom.development.js 在 commitRoot 上面打了一个断点这个断点是看最终要进行commit的时候它是如何去获取哪几个节点是要更新的也就是我们的 SideEffect 链是怎么样的一个形式, 点一下断点被捕获捕获了之后可以看到它接收的两个参数一个是root一个是finishedWork那么它们其实是一个对应的关系 root.current.alternate 就等于 finishedWork因为 finishedWork 对应的是 workInProgressfinishedWork 上面就会记录我们当前这一次更新它所有需要去执行的就是要更新到dom上面的一些内容 它的记录就在 firstEffect 到 lastEffect 的一个链上它这边 firstEffect 记录的 elementType 是一个 span所谓 effect 它最终是一个fiber节点就是告诉我们哪一个fiber节点需要更新对于我们这边第二个span它的值是要被更新成4的所以可以看到它的 firstEffect 的第一个节点是 span它的 effectTag是 4然后它的 updateQueen 的属性里面是一个数组 updateQueue: [children, 4]这个大致意思可以看出我们要把这个span标签对应的dom节点, 它的children里面显示的文字内容从2变成4还有就是下一个节点, 也是一个span, 看 nextEffect, 它的 elementType 也是span它的 updateQueen 是 updateQueue: [children, 9]也就是原值为 3 的节点 对于commitWork只会关心 firstEffect 到 lastEffect 这个链条上面的对应的 fiber 节点 它只需要更新的就是这两个dom节点的children的一个变化 这就是react里面的 vdom它以最小化的程度去更新 dom而不需要对其他的任何节点再进行一个操作以此来提升一个性能这个过程, 就是在 v16.6.3/packages/react-reconciler/src/ReactFiberScheduler.js#L1026 这个 if 里面被实现的这个 demo 示例对应最最上面的流程图来说 这边点了 button之后在这里创建了一个update就是这个List上面, 通过调用 this.setState 去创建了这个 update最终是要从 RootFiber 上面往下进行一个更新更新到 List 中的第一个span以及后续的span然后发现只有第2和第3个span它们的 children从 2 变成了 4从 3 变成了 9它们两个对应的 SideEffect 是 Update 对应值为4 (二进制的表示形式, 参考 ReactSideEffectTags.js)它们要把内容挂载到dom上面第一个span的内容因为是1乘以1所以它没有变化就不需要执行真正的更新首先执行 completeUnitOfWork 的是 第一个 span, 接下去它的 sibling因为第一个 span 没有 SideEffect在这里它的 returnFiber 就是 List当前的 workInProgress 就是这第一个 span这个span没有任何的 SideEffect这个 List 自己也没有 SideEffect所以赋值List的时候对List也是没有任何的一个更新的, 如下if (returnFiber.firstEffect null) {returnFiber.firstEffect workInProgress.firstEffect;
}这时候的 List 本身的 firstEffect 到 lastEffect是没有任何内容的也就是null进行到第2个span节点它自己有 SideEffect, 而它没有子节点所以它本身上面的 firstEffect到lastEffect是没有任何内容的唯一需要操作的是要把它作为List的第一个 SideEffect 增加到这个链上面因为是 Update, 值为 4 肯定是大于 Performedwork 值为 1的if (effectTag PerformedWork) {if (returnFiber.lastEffect ! null) {returnFiber.lastEffect.nextEffect workInProgress; // 情况1标记为 L1, 以便下面引用说明} else {returnFiber.firstEffect workInProgress; // 情况2, 标记为 L2}returnFiber.lastEffect workInProgress; // 通用设定3标记为 L3
}它自身(workInProgress)要作为 returnFiber 的第一个 SideEffect给它增加到 List 的firstEffect到lastEffect的链上所以returnFiber的 firstEffect 和 lastEffect都等于这个Fiber对象(第二个span) workInProgress对应的执行语句是 L2 和 L3当第2个span执行complete完了又要开始它的sibling, 也就是第三个span第三个span也是有更新的所以它也要增加到 List上面这个时候List上面已经有变化因为List.lastEffect(姑且这样表示)已经不等于null了这个时候执行的是 else 的情况即 L1因为在上一个节点的complete过程当中已经指定了 firstEffect 和 lastEffect 都为 workInProgress(第2个span)所以第二个span节点的 nextEffect 指向的是第三个span的节点它们更新完之后又要更新 button因为它没有任何的变化所以 button 也没有更新这个时候对于List来说它的 SideEffect 链是等于 span指向span的这么一个过程List的firstEffect指向第二个span对应的Fiber对象lastEffect指向第三个span对应的Fiber对象List本身执行 completeUnitOfWork 的时候自己没有任何的更新因为它的state更新对于真正的dom操作没有任何的关系而且它也没有生命周期方法所以 List 后期没有任何要做的事情了, 自己本身是没有 SideEffect只有子节点的 SideEffect在 List 执行 completeUnitOfWork 的时候, 代码中的 returnFiber 对应是 div而 workInProgress 对应的是 List, 而 List 要赋值它的 firstEffect 和 lastEffect它的链指向returnFiber, 就是要放到 returnFiber 它的链的最后面returnFiber 这个 div它目前是肯定没有任何的 SideEffect的, 所以它直接赋值成 workInProgress.firstEffect也就是说对于 div 它的 firstEffect 跟 lastEffect 已经变成 List 的 firstEffect 跟 lastEffect因为上面节点都是没有任何更新的, 所以一层一层往上之后, 在 RootFiber 上面记录的firstEffect 跟 lastEffect 也就变成了最开始的两个span所以这就是通过 completeUnitOfWork最终能够赋值到 RootFiber 上面在它的 firstEffect 到 lastEffect单项链表上面去记录整个应用当中所有需要去更新的最终的dom节点以及组件的fiber对象的一个过程这里涉及到后期 commitRoot 的时候, 调用这个单项链表一个过程这里先跳过 对于 unwindWork 的一个过程, 也是相似的, 先跳过 unwindWork 以及 completeWork目前只关心它的一个遍历过程跳过具体节点做的事情, 关注它的大致流程它真正帮我们去实现了把所有需要更新的节点进行一个串联让最终 commitRoot 的时候能够方便获取到所有需要更新的节点的一个过程
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/82517.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!