Error Boundaries
- 可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI
usage
class 组件的两个生命周期函数(componentDidCatch | getDerivedStateFromError)
- 只有
getDerivedStateFromError 存在才会渲染 fallback UI吗?
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
原理: 发生错误重新构造更新对象并重新发起调度.
- 不管是
handleError还是captureCommitPhaseError,都会从发生错误的节点的父节点开始,逐层向上遍历,寻找最近的Error Boundaries
- 存在
Error Boundaries则
- 构造 执行Error Boundaries API的
callback
- 构造 抛出React提示信息
callback
render phase (handleError)
function ErrorComp() {
throw new Error('error happen here')
return <div>
</div>
}
handleError: 异常处理
- 在
renderRootSync中 会通过try/catch 捕获 同步执行工作循环的主函数(workLoopSync)的异常
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
throwException : 创建错误的更新对象
- 首先将当前fiber标记为
Incomplete , 然后从发生错误的fiber节点一直往上找, 直到找到最近的Error Boundaries, 并创建错误的更新对象(createClassErrorUpdate getDerivedStateFromError作为payload ,componentDidCatch作为callback),并将其添加的fiber.updateQueue中.
if (typeof getDerivedStateFromError === 'function') {
const error = errorInfo.value;
update.payload = () => {
logCapturedError(fiber, errorInfo);
return getDerivedStateFromError(error);
};
}
if (inst !== null && typeof inst.componentDidCatch === 'function') {
update.callback = function callback() {
const error = errorInfo.value;
const stack = errorInfo.stack;
this.componentDidCatch(error, {
componentStack: stack !== null ? stack : '',
});
};
}
completeUnitOfWork: 找到 Error Boundaries 并作为 WIP 返回
erroredWork.flags === Incomplete: 将 erroredWork的所有returnFiber标记为 Incomplete,让其进入 (completedWork.flags & Incomplete) !== NoFlags的逻辑, 直到找到 Error Boundaries 并作为 workInProgress 返回(注意这里还会添加 DidCapture flag).重新进入到Reconciler阶段
updateClassComponent: 调用 getDerivedStateFromError 生命周期函数
- 如果
getDerivedStateFromError存在 , 则会在 updateClassComponent -> resumeMountClassInstance —> processUpdateQueue -> getStateFromUpdate 中将被处理.返回的状态将被合并到 state中
case CaptureUpdate: {
workInProgress.flags =
(workInProgress.flags & ~ShouldCapture) | DidCapture;
}
case UpdateState: {
const payload = update.payload;
let partialState;
if (typeof payload === 'function') {
partialState = payload.call(instance, prevState, nextProps);
- 如果
componentDidCatch 存在, 则会作为 update.callback在commit阶段被处理 [[commit#layout commitLayoutEffects]]
const callback = update.callback;
if (callback !== null) {
workInProgress.flags |= Callback;
const effects = queue.effects;
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
}
finishClassComponent : 当前fiber 如果 flags === DidCapture ,则表示是 Error Boundaries 组件, 且只有 getDerivedStateFromError 存在, 才会继续调和子fiber(reconcileChildren), 否则 卸载所有的子fiber(即nextChildren=null)
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderLanes: Lanes,
) {
markRef(current, workInProgress);
const didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;
if (!shouldUpdate && !didCaptureError) {
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
const instance = workInProgress.stateNode;
ReactCurrentOwner.current = workInProgress;
let nextChildren;
if (
didCaptureError &&
typeof Component.getDerivedStateFromError !== 'function'
) {
nextChildren = null;
} else {
nextChildren = instance.render();
}
if (current !== null && didCaptureError) {
forceUnmountCurrentAndReconcile(
current,
workInProgress,
nextChildren,
renderLanes,
);
} else {
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
workInProgress.memoizedState = instance.state;
return workInProgress.child;
}
commit phase (captureCommitPhaseError)
function ErrorComp() {
useEffect(()=>{
throw new Error('error happen here')
},[])
return <div>
</div>
}
captureCommitPhaseError: 捕获 commit阶段出现的异常
- 在 [[useEffect#commit 阶段]] , 调用
create的时候, 如果发生异常将被 captureCommitPhaseError 捕获
const create = effect.create;
try{
effect.destroy = create();
} catch (error) {
captureCommitPhaseError(fiber, error);
}
captureCommitPhaseError: 做的工作就是找到 Error Boundaries fiber并创建更新对象并调度更新,注意是从 fiberRoot开始调度进入 workLoopSync.
updateComponentInstance: 调用 getDerivedStateFromError 生命周期函数
- 如果
getDerivedStateFromError存在 , 则会在 updateClassComponent -> updateClassInstance —> processUpdateQueue -> getStateFromUpdate 中将被处理.返回的状态将被合并到 state中
commitLayoutEffectOnFiber: 调用 componentDidCatch生命周期函数
componentDidCatch: 将作为 [[commit#layout commitLayoutEffects]] setState的 callback 通过 commitUpdateQueue 调用
export function commitUpdateQueue<State>(
finishedWork: Fiber,
finishedQueue: UpdateQueue<State>,
instance: any,
): void {
const effects = finishedQueue.effects;
finishedQueue.effects = null;
if (effects !== null) {
for (let i = 0; i < effects.length; i++) {
const effect = effects[i];
const callback = effect.callback;
if (callback !== null) {
effect.callback = null;
callCallback(callback, instance);
}
}
}
}