react-reconciler 版本 0.26.1
本文是学习笔记。
流程:
-
捕获错误
render 阶段
// renderRootSync do { try { workLoopSync(); break; } catch (thrownValue) { handleError(root, thrownValue); } } while (true);commit 阶段
// commitRootImpl do { try { commitBeforeMutationEffects(); } catch (error) { invariant(nextEffect !== null, 'Should be working on an effect.'); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; } } while (nextEffect !== null);可以发现,即使没有Error Boundaries,「工作流程」中的错误已经被React捕获了。而正确的逻辑应该是:
-
如果存在Error Boundaries,执行对应API
-
抛出React的提示信息
-
如果不存在Error Boundaries,抛出「未捕获的错误」
所以,不管是handleError还是captureCommitPhaseError,都会从发生错误的节点的父节点开始,逐层向上遍历,寻找最近的Error Boundaries。
-
-
构建 update
createRootErrorUpdate/createClassErrorUpdatefunction createRootErrorUpdate( fiber: Fiber, errorInfo: CapturedValue<mixed>, lane: Lane, ): Update<mixed> { const update = createUpdate(NoTimestamp, lane); // Unmount the root by rendering null. update.tag = CaptureUpdate; update.payload = {element: null}; const error = errorInfo.value; update.callback = () => { onUncaughtError(error); logCapturedError(fiber, errorInfo); }; return update; } function createClassErrorUpdate( fiber: Fiber, errorInfo: CapturedValue<mixed>, lane: Lane, ): Update<mixed> { const update = createUpdate(NoTimestamp, lane); update.tag = CaptureUpdate; const getDerivedStateFromError = fiber.type.getDerivedStateFromError; if (typeof getDerivedStateFromError === 'function') { const error = errorInfo.value; update.payload = () => { return getDerivedStateFromError(error); }; update.callback = () => { logCapturedError(fiber, errorInfo); }; } const inst = fiber.stateNode; if (inst !== null && typeof inst.componentDidCatch === 'function') { update.callback = function callback() { logCapturedError(fiber, errorInfo); if (typeof getDerivedStateFromError !== 'function') { markLegacyErrorBoundaryAsFailed(this); } const error = errorInfo.value; const stack = errorInfo.stack; this.componentDidCatch(error, { componentStack: stack !== null ? stack : '', }); }; } return update; } -
触发更新
captureCommitPhaseError/captureCommitPhaseErrorfunction captureCommitPhaseErrorOnRoot( rootFiber: Fiber, sourceFiber: Fiber, error: mixed, ) { const errorInfo = createCapturedValue(error, sourceFiber); const update = createRootErrorUpdate(rootFiber, errorInfo, (SyncLane: Lane)); enqueueUpdate(rootFiber, update, (SyncLane: Lane)); const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane)); if (root !== null) { markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); } } export function captureCommitPhaseError( sourceFiber: Fiber, nearestMountedAncestor: Fiber | null, error: mixed, ) { if (sourceFiber.tag === HostRoot) { // Error was thrown at the root. There is no parent, so the root // itself should capture it. captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error); return; } while (fiber !== null) { if (fiber.tag === HostRoot) { captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error); return; } else if (fiber.tag === ClassComponent) { const ctor = fiber.type; const instance = fiber.stateNode; if ( typeof ctor.getDerivedStateFromError === 'function' || (typeof instance.componentDidCatch === 'function' && !isAlreadyFailedLegacyErrorBoundary(instance)) ) { const errorInfo = createCapturedValue(error, sourceFiber); const update = createClassErrorUpdate( fiber, errorInfo, (SyncLane: Lane), ); enqueueUpdate(fiber, update, (SyncLane: Lane)); const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane)); if (root !== null) { markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); } return; } } fiber = fiber.return; } }可以看到
update构建好了之后,就调用了enqueueUpdate将这次更新放到更新队列中,并且发起了调度。这次更新后,将会更新getDerivedStateFromError(error)的返回值且会在layout阶段打印报错信息,如果componentDidCatch方法存在,将会触发调用。
参考
[Error Boundaries是这么实现的,还挺巧妙] mp.weixin.qq.com/s/pEBQdvZIP…
[createClassErrorUpdate] github.com/facebook/re…
[createRootErrorUpdate] github.com/facebook/re…
[renderRootSync] github.com/facebook/re…
[handleError] github.com/facebook/re…
[captureCommitPhaseErrorOnRoot] github.com/facebook/re…
[captureCommitPhaseError] github.com/facebook/re…
[commitRootImpl] github.com/facebook/re…