React Fiber 错误记录器
一、作用
二、默认在未捕获错误时
export function defaultOnUncaughtError(
error: mixed,
// errorInfo: {+componentStack?: ?string},
errorInfo: { componentStack?: ?string },
): void {
// Overriding this can silence these warnings e.g. for tests.
// See https://github.com/facebook/react/pull/13384
//
// 重写此方法可以消除这些警告,例如在测试中。
// 参见 https://github.com/facebook/react/pull/13384
// For uncaught root errors we report them as uncaught to the browser's
// onerror callback. This won't have component stacks and the error addendum.
// So we add those into a separate console.warn.
//
// 对于未捕获的根错误,我们将它们报告为浏览器的 onerror 回调未捕获的错误。
// 这将不会包含组件堆栈和错误附加信息。
// 因此,我们将这些信息添加到单独的 console.warn 中。
reportGlobalError(error);
if (__DEV__) {
const componentNameMessage = componentName
? `An error occurred in the <${componentName}> component.`
: 'An error occurred in one of your React components.';
const errorBoundaryMessage =
'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
'Visit https://react.dev/link/error-boundaries to learn more about error boundaries.';
try {
console.warn(
'%s\n\n%s\n',
componentNameMessage,
errorBoundaryMessage,
// We let our console.error wrapper add the component stack to the end.
// 我们让我们的 console.error 包装器在末尾添加组件堆栈。
);
} finally {
// ignore
// 忽略
}
}
}
三、默认捕获错误
备注
bindToConsole()由宿主环境提供
export function defaultOnCaughtError(
error: mixed,
errorInfo: {
// +componentStack?: ?string,
componentStack?: ?string;
// +errorBoundary?: ?component(...props: any),
errorBoundary?: ?Component;
},
): void {
// Overriding this can silence these warnings e.g. for tests.
// See https://github.com/facebook/react/pull/13384
//
// 重写此方法可以消除这些警告,例如在测试中。
// 参见 https://github.com/facebook/react/pull/13384
// Caught by error boundary
// 被错误边界捕获
if (__DEV__) {
const componentNameMessage = componentName
? `The above error occurred in the <${componentName}> component.`
: 'The above error occurred in one of your React components.';
// In development, we provide our own message which includes the component stack
// in addition to the error.
//
// 在开发过程中,我们会提供我们自己的消息,其中包含组件堆栈,除了错误信息之外。
const recreateMessage =
`React will try to recreate this component tree from scratch ` +
`using the error boundary you provided, ${
errorBoundaryName || 'Anonymous'
}.`;
try {
if (
typeof error === 'object' &&
error !== null &&
typeof error.environmentName === 'string'
) {
// This was a Server error. We print the environment name in a badge just like we do with
// replays of console logs to indicate that the source of this throw as actually the Server.
//
// 这是服务器错误。我们会像重播控制台日志一样,在徽章中打印环境名称,以表明抛出此错误的源实际上是服务器。
bindToConsole(
'error',
[
'%o\n\n%s\n\n%s\n',
error,
componentNameMessage,
recreateMessage,
// We let DevTools or console.createTask add the component stack to the end.
// 我们允许 DevTools 或 console.createTask 将组件堆栈添加到末尾。
],
error.environmentName,
)();
} else {
console.error(
'%o\n\n%s\n\n%s\n',
error,
componentNameMessage,
recreateMessage,
// We let our DevTools or console.createTask add the component stack to the end.
// 我们让我们的 DevTools 或 console.createTask 将组件堆栈添加到末尾。
);
}
} finally {
// ignore
// 忽略
}
} else {
// In production, we print the error directly.
// This will include the message, the JS stack, and anything the browser wants to show.
// We pass the error object instead of custom message so that the browser displays the error natively.
//
// 在生产环境中,我们直接打印错误。
// 这将包括错误信息、JS 调用堆栈,以及浏览器想要显示的任何内容。
// 我们传递错误对象而不是自定义信息,以便浏览器可以原生显示错误。
console['error'](error); // Don't transform to our wrapper, however, React DevTools can still add a stack.
// 不要转换为我们的包装器,但 React DevTools 仍然可以添加堆栈。
}
}
四、记录未捕获的错误
备注
getComponentNameFromFiber()由 getComponentNameFromFiber 实现
export function logUncaughtError(
root: FiberRoot,
errorInfo: CapturedValue<mixed>,
): void {
try {
if (__DEV__) {
componentName = errorInfo.source
? getComponentNameFromFiber(errorInfo.source)
: null;
errorBoundaryName = null;
}
const error = errorInfo.value as any;
if (__DEV__ && ReactSharedInternals.actQueue !== null) {
// For uncaught errors inside act, we track them on the act and then
// rethrow them into the test.
//
// 对于 act 内未捕获的错误,我们会在 act 中跟踪它们,然后将它们重新抛入测试中。
ReactSharedInternals.thrownErrors.push(error);
return;
}
const onUncaughtError = root.onUncaughtError;
onUncaughtError(error, {
componentStack: errorInfo.stack,
});
} catch (e) {
// This method must not throw, or React internal state will get messed up.
// If console.error is overridden, or logCapturedError() shows a dialog that throws,
// we want to report this error outside of the normal stack as a last resort.
// https://github.com/facebook/react/issues/13188
//
// 这个方法不能抛出异常,否则 React 的内部状态将会混乱。
// 如果 console.error 被重写,或者 logCapturedError() 显示一个会抛出异常的对话框,
// 我们希望将这个错误作为最后的手段在正常堆栈之外报告。
// https://github.com/facebook/react/issues/13188
setTimeout(() => {
throw e;
});
}
}
五、记录捕获的错误
备注
getComponentNameFromFiber()由 getComponentNameFromFiber 实现
export function logCaughtError(
root: FiberRoot,
boundary: Fiber,
errorInfo: CapturedValue<mixed>,
): void {
try {
if (__DEV__) {
componentName = errorInfo.source
? getComponentNameFromFiber(errorInfo.source)
: null;
errorBoundaryName = getComponentNameFromFiber(boundary);
}
const error = errorInfo.value as any;
const onCaughtError = root.onCaughtError;
onCaughtError(error, {
componentStack: errorInfo.stack,
errorBoundary:
boundary.tag === ClassComponent
? boundary.stateNode // This should always be the case as long as we only have class boundaries
: // 只要我们只有类边界,这种情况就应该始终成立
null,
});
} catch (e) {
// This method must not throw, or React internal state will get messed up.
// If console.error is overridden, or logCapturedError() shows a dialog that throws,
// we want to report this error outside of the normal stack as a last resort.
// https://github.com/facebook/react/issues/13188
//
// 这个方法绝对不能抛出异常,否则 React 的内部状态会被破坏。
// 如果 console.error 被重写,或者 logCapturedError() 显示了会抛出异常的对话框,
// 我们希望作为最后手段在正常堆栈之外报告这个错误。
// https://github.com/facebook/react/issues/13188
setTimeout(() => {
throw e;
});
}
}
六、可恢复错误默认处理
export function defaultOnRecoverableError(
error: mixed,
errorInfo: {+componentStack?: ?string},
) {
reportGlobalError(error);
}
七、变量
// Side-channel since I'm not sure we want to make this part of the public API
// 侧通道,因为我不确定我们是否希望将其作为公共 API 的一部分
let componentName: null | string = null; // 组件名称
let errorBoundaryName: null | string = null; // 错误边界名称