1 )概述
renderRoot
的 throw Exception 里面, 对于被捕获到错误的组件进行了一些处理getDerivedStateFromError
或者 componentDidCatch
这样的生命周期方法completeWork
,对于不同组件, 进行一些不同的处理
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) {
// ... 跳过很多代码
// 符合这个条件,走的是 completeWork
if ((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
这个运算中,这边可能会有一个问题unwindWork
function 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 boundary's `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 it's the same as the current tree's.
// Clone the object.
nextState = {
alreadyCaptured: true,
didTimeout: nextState.didTimeout,
timedOutAt: nextState.timedOutAt,
};
} else {
// Already have a clone, so it's 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;
}
}
还是拿出之前的图来说这个整体流程
getDerivedStateFromError
或者 componentDidCatch
这样的生命周期方法getDerivedStateFromError
或者 componentDidCatch
completeUnitOfWork
第一个节点是 List 这个组件
completeUnitOfWork
之后,仍然走的是 unwindWork
completeUnitOfWork
相当于是return了一个Fiber对象completeUnitOfWork
返回的 nextUnitOfWork 对象workLoop
performUnitOfWork
,然后调用 beginWork
updateClassComponent
updateClassComponent
updateClassInstance
processUpdateQueue
createClassErrorUpdate
getDerivedStateFromError
,以及它会有一个callback是调用 componentDidCatchgetDerivedStateFromError
这个方法,对应的在 ClassComponent 里面processUpdateQueue
的时候,肯定会调用这个方法beginWork
,再往下调用 finishClassComponent
的时候if ( didCaptureError && typeof Component.getDerivedStateFromError !== 'function') {}
didCaptureError
来自于我们这个组件上面是否有 DidCapture 这个 SideEffectif (current !== null && didCaptureError) {}
forceUnmountCurrentAndReconcile
function forceUnmountCurrentAndReconcile(
current: Fiber,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
// This function is fork of reconcileChildren. It's 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, we're 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,
);
}
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {}
```
* 这边需要注意的是, 它传入的 newChild 是 null
* 就是说它要强制把目前所有的子树的节点全部给它删掉
* 它渲染出没有子树的 ClassComponent
getDerivedStateFromError
或者 componentDidCatch
unwindWork
处理流程当中需要注意的一些点