电子产品展示网站模板,轻应用网站模板,平江高端网站建设,大连手机自适应网站建设维护unwindWork 1 #xff09;概述
在 renderRoot 的 throw Exception 里面, 对于被捕获到错误的组件进行了一些处理并且向上去寻找能够处理这些异常的组件#xff0c;比如说 class component 里面具有getDerivedStateFromError 或者 componentDidCatch 这样的生命周期方法这个c…unwindWork 1 概述
在 renderRoot 的 throw Exception 里面, 对于被捕获到错误的组件进行了一些处理并且向上去寻找能够处理这些异常的组件比如说 class component 里面具有getDerivedStateFromError 或者 componentDidCatch 这样的生命周期方法这个class component 就代表它可以处理它的子树当中渲染出来的任何的错误但是在这个过程当中只是在上面增加了一些 SideEffect 比如说, 在出错的那个组件上增加了 Incomplete而对于能够处理这个错误的组件增加了 ShouldCapture 这些 SideEffect 最终会被如何进行处理, 这时候就要用到 unwindWork 了类似于 completeWork对于不同组件, 进行一些不同的处理 它整个流程是跟 completeWork 完全区分开的它当然也要去做一些 completWork 里面会做的一些工作但是它们肯定会有一些区别不然它们也不需要进行一个区分 对于 ShouldCapture 组件会设置 DidCapture 副作用
2 源码
定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L1354
定位到 throwException
throwException(root,returnFiber,sourceFiber, // 报错的那个组件thrownValue,nextRenderExpirationTime,
);
nextUnitOfWork completeUnitOfWork(sourceFiber);
continue;进入 throwException 下面是精简版只看结构 function throwException(root: FiberRoot,returnFiber: Fiber,sourceFiber: Fiber,value: mixed,renderExpirationTime: ExpirationTime,
) {// 添加 Incomplete// The source fiber did not complete.sourceFiber.effectTag | Incomplete;// Its effect list is no longer valid.// 清空 Effect 链sourceFiber.firstEffect sourceFiber.lastEffect null;// ... 其他代码忽略进入 completeUnitOfWork 下面是精简版只看结构 function completeUnitOfWork(workInProgress: Fiber): Fiber | null {while (true) {// ... 跳过很多代码// 符合这个条件走的是 completeWorkif ((workInProgress.effectTag Incomplete) NoEffect) {// This fiber completed.if (enableProfilerTimer) {// ... 跳过很多代码nextUnitOfWork completeWork(current,workInProgress,nextRenderExpirationTime,);// ... 跳过很多代码} else {nextUnitOfWork completeWork(current,workInProgress,nextRenderExpirationTime,);}// ... 跳过很多代码} else {// ... 跳过很多代码// 否则走的是 unwindWork, 对于不同的组件这个返回值也会不同const next unwindWork(workInProgress, nextRenderExpirationTime);// ... 跳过很多代码if (next ! null) {// ... 跳过很多代码next.effectTag HostEffectMask; // 注意这个运算只有在 当前effect和HostEffectMask共有的才会最终留存下来return next; // 看到这边 return 了 next}if (returnFiber ! null) {// Mark the parent fiber as incomplete and clear its effect list.returnFiber.firstEffect returnFiber.lastEffect null;returnFiber.effectTag | Incomplete;}}}return null;
}在 next.effectTag HostEffectMask 这个运算中这边可能会有一个问题Incomplete 是在 sourceFiber 上面也就是说报错的那个组件上面的但是它向上寻找的就是能够处理错误的组件, 它增加的是 ShouldCapture需要注意的一点是一开始进来处理的第一个组件它是报错的那个组件它并不一定是能够处理错误的那个组件因为在 unwindWork 里面增加这个 ShouldCapture 的时候是我们在一个循环中向报错的那个组件的父链上面去寻找可以处理错误的那个组件一般它是 HostRoot 或者是 ClassComponent所以一进来的时候处理的组件是报错那个组件它可能不是一个ClassComponent或者它没有错误处理的能力这个时候它不一定会进来这个 next ! null 的这个判断那么它会往下走那往下走的时候它判断了returnfivever不等于诺的情况它会去给 returnFiber 增加这个 Incomplete 的 effectTag也就是说如果一个子树当中的组件报错了对于它父链上的所有组件的 completeUnitOfWork都会执行对应的 unwindWork 的流程而不会走 completeWork的流程进入 unwindWorkfunction unwindWork(workInProgress: Fiber,renderExpirationTime: ExpirationTime,
) {switch (workInProgress.tag) {// 在这里面它根据不同的组件类型处理了这些内容// 它主要处理的就是 ClassComponent, HostComponent SuspenseComponent // 剩下的一些基本上都是跟 completeWork 里面类似// 对于这些组件它跟 completeWork 最大的区别就是它会去判断 ShouldCapture 这个 SideEffect// 如果我们这个组件上面有 ShouldCapture 这个 SideEffect// 那么它会把 ShouldCapture 给它去掉 然后增加这 DidCapture 这个 SideEffect// 这就是对于 classComponent 跟 completeWork 里面的一个最主要的区别case ClassComponent: {const Component workInProgress.type;if (isLegacyContextProvider(Component)) {popLegacyContext(workInProgress);}const effectTag workInProgress.effectTag;if (effectTag ShouldCapture) {// 注意这里workInProgress.effectTag (effectTag ~ShouldCapture) | DidCapture;return workInProgress;}return null;}// 与 ClassComponent 类似case HostRoot: {popHostContainer(workInProgress);popTopLevelLegacyContextObject(workInProgress);const effectTag workInProgress.effectTag;invariant((effectTag DidCapture) NoEffect,The root failed to unmount after an error. This is likely a bug in React. Please file an issue.,);// 注意这里workInProgress.effectTag (effectTag ~ShouldCapture) | DidCapture;return workInProgress;}case HostComponent: {popHostContext(workInProgress);return null;}case SuspenseComponent: {const effectTag workInProgress.effectTag;if (effectTag ShouldCapture) {workInProgress.effectTag (effectTag ~ShouldCapture) | DidCapture;// Captured a suspense effect. Set the boundarys alreadyCaptured// state to true so we know to render the fallback.const current workInProgress.alternate;const currentState: SuspenseState | null current ! null ? current.memoizedState : null;let nextState: SuspenseState | null workInProgress.memoizedState;if (nextState null) {// No existing state. Create a new object.nextState {alreadyCaptured: true,didTimeout: false,timedOutAt: NoWork,};} else if (currentState nextState) {// There is an existing state but its the same as the current trees.// Clone the object.nextState {alreadyCaptured: true,didTimeout: nextState.didTimeout,timedOutAt: nextState.timedOutAt,};} else {// Already have a clone, so its safe to mutate.nextState.alreadyCaptured true;}workInProgress.memoizedState nextState;// Re-render the boundary.return workInProgress;}return null;}case HostPortal:popHostContainer(workInProgress);return null;case ContextProvider:popProvider(workInProgress);return null;default:return null;}
}注意ClassComponent和HostRoot的return内容对于HostRoot来说所有情况下都 return workInProgress而对于 ClassComponent 在有 ShouldCapture 的时候return的是当前 workInProgress, 否则是return null 还是拿出之前的图来说这个整体流程 比如说上面这张图里面 List 这个组件它渲染的时候报错了但是它没有 getDerivedStateFromError 或者 componentDidCatch 这样的生命周期方法那么它是不会增加 ShouldCapture 这个 SideEffect 的如果这个时候App组件具有 getDerivedStateFromError 或者 componentDidCatch这个时候 App 增加的是 ShouldCapture 这个 SideEffect在 throw Exception 处理完之后要执行的 completeUnitOfWork 第一个节点是 List 这个组件 也就是说它执行的时候它里面的 next 是 null会继续往下面的 returnFiber 判断中去得到的 returnFiber 是 div, 对 div 增加了这些 SideEffect 之后 它又会走 completeUnitOfWork 之后仍然走的是 unwindWork这时的 div 是一个 HostComponent它不会处理错误然后又走到下一个走到 App到 unwindWork 之后发现它是有 ShouldCapture 这个 SideEffect 的它 return 的是 workInProgress之后有next了这边就可以 return next并且它上面会具有 DidCapture 的 SideEffect它 return next 之后对于 completeUnitOfWork 相当于是return了一个Fiber对象也就是说 renderRoot 处理异常后面 completeUnitOfWork 返回的 nextUnitOfWork 对象就变成了App组件它对应的Fiber对象也就是说nextUnitOfWork 现在等于App我们的 do while 循环仍然要继续依然继续调用 workLoop调用 workLoop 就会调用 performUnitOfWork然后调用 beginWork所以对于 App 这个组件又需要去重新走一遍更新的流程但是走更新的流程的时候这个时候已经不一样了不一样在哪里呢 App它是一个 ClassComponent所以我们要走的是 updateClassComponent在 ReactFiberBeginWork.js 中找到 updateClassComponent这边正常走下来其实都是差不多的创建这些流程之类的这边需要注意的是比如说 updateClassInstance它对应的是在 ReactFiberClassComponent.js 里面找到这个方法在这个方法里面它会去 processUpdateQueue在按 unwindWork的时候给ClassComponent 创建了一个update 叫 createClassErrorUpdate这个 update 它会去调用 getDerivedStateFromError以及它会有一个callback是调用 componentDidCatch所以如果有 getDerivedStateFromError 这个方法对应的在 ClassComponent 里面在进行 processUpdateQueue 的时候肯定会调用这个方法它就会计算出有错误的情况下它的一个state这个state就会引导 ClassComponent 去渲染错误相关的UI这就是我们的组件 ClassComponent 去捕获错误并且去渲染出错误相关UI的一个流程因为渲染的是错误相关的UI, 所以原先的它的子树肯定是不会再被渲染出来的或者有可能会被渲染出来, 这个情况视具体的内容而定 在这种情况下再回到 beginWork再往下调用 finishClassComponent 的时候有一个判断是 if ( didCaptureError typeof Component.getDerivedStateFromError ! function) {} didCaptureError 来自于我们这个组件上面是否有 DidCapture 这个 SideEffect在有错误的情况下它这个属性肯定是 true并且没有 getDerivedStateFromError 的情况先不管继续往下看 下面的一个判断 if (current ! null didCaptureError) {} 它会执行的一个方法是 forceUnmountCurrentAndReconcile这个情况就是最常见的一个情况就是组件是在一个更新的过程当中然后它的子树出现了错误这个App要去渲染错误它这边的 didCaptureError 就是 truefunction forceUnmountCurrentAndReconcile(current: Fiber,workInProgress: Fiber,nextChildren: any,renderExpirationTime: ExpirationTime,
) {// This function is fork of reconcileChildren. Its used in cases where we// want to reconcile without matching against the existing set. This has the// effect of all current children being unmounted; even if the type and key// are the same, the old child is unmounted and a new child is created.//// To do this, were going to go through the reconcile algorithm twice. In// the first pass, we schedule a deletion for all the current children by// passing null.workInProgress.child reconcileChildFibers(workInProgress,current.child,null,renderExpirationTime,);// In the second pass, we mount the new children. The trick here is that we// pass null in place of where we usually pass the current child set. This has// the effect of remounting all children regardless of whether their their// identity matches.workInProgress.child reconcileChildFibers(workInProgress,null,nextChildren,renderExpirationTime,);
}1 它先调用了一遍 reconcileChildFibers, 先看下它的构造结构 function reconcileChildFibers(returnFiber: Fiber,currentFirstChild: Fiber | null,newChild: any,expirationTime: ExpirationTime,): Fiber | null {}* 这边需要注意的是, 它传入的 newChild 是 null
* 就是说它要强制把目前所有的子树的节点全部给它删掉
* 它渲染出没有子树的 ClassComponent2 然后再渲染一遍 这个时候传入的 currentFirstChild (老的 children) 是 null然后传入的 newChild 是 nextChildren 因为这个 nextchildren 是我们已经从新的(有错误的)update里面, 计算出的一个新的 state这个children, 一般来说它跟老的 children 是完全不一样的就算不是完全不一样也可能是大部分不一样它强制在第一次的时候直接用 null, 把它的子树给清空然后渲染新的 children 这样的效率会更高一点就不需要通过key去对比这些流程了 这就是在react 它的 error boundary 这个功能 提供我们 ClassComponent , 使用 getDerivedStateFromError 或者 componentDidCatch这样的生命周期方法来处理捕获到错误之后的一个流程 同样的, 这是 unwindWork 处理流程当中需要注意的一些点