Fiber 组件栈
ReactFiberComponentStack 用于 错误堆栈跟踪 的关键模块,负责在组件渲染过程中捕获和构建组件调用堆栈,当发生错误时生成可读的组件堆栈信息,以便在错误边界 (Error Boundaries )、警告( warnings ) 或调试信息中提供清晰、刻度的组件堆栈追踪 。
- 利用 Fiber 树的父子关系回溯组件调用链
- 仅在错误发生时生成堆栈,平衡性能和可调试性
- 为开发者提供清晰的组件级错误定位
- 深度集成于 React Reconciler 的异常处理流程
一、为什么需要
- 原生 JavaScript 调用栈不能反映组件的真实嵌套关系(迭代遍历会打破递归调用链)
- 如组件渲染出错,原生 JS 栈会充满 Fiber 内部逻辑( 如
performUnitOfWork),无法直接定位到用户编写的组件层级
二、关键细节和边界处理
- 过滤无用组件 : HostComponent (如 div 、 span )默认会显示,但可通过配置隐藏(React 内部会忽略部分原生标签的栈展示)
- 匿名组件处理 : 无
displayName和函数名的组件,fallback显示为 「ANonymous」 - 生产环境剔除 : 所有逻辑被
__DEV__包裹,生产环境不会引入额外的开销 - 调试信息依赖 :
_debugSource由 Babel 插件插入,若关闭该插件(如生产环境编译),组件栈将不显示文件路径和行号
三、在开发和生产中通过 Fiber 获取堆栈
export function getStackByFiberInDevAndProd(workInProgress: Fiber): string {
try {
let info = '';
let node: Fiber = workInProgress;
let previous: null | Fiber = null;
do {
info += describeFiber(node, previous);
if (__DEV__) {
// Add any Server Component stack frames in reverse order.
const debugInfo = node._debugInfo;
if (debugInfo) {
for (let i = debugInfo.length - 1; i >= 0; i--) {
const entry = debugInfo[i];
if (typeof entry.name === 'string') {
info += describeDebugInfoFrame(
entry.name,
entry.env,
entry.debugLocation,
);
}
}
}
}
previous = node;
// $FlowFixMe[incompatible-type] we bail out when we get a null
node = node.return;
} while (node);
return info;
} catch (x) {
return '\nError generating stack: ' + x.message + '\n' + x.stack;
}
}
四、在开发环境中通过 Fiber 获取所有者堆栈
function describeFunctionComponentFrameWithoutLineNumber(fn: Function): string {
// We use this because we don't actually want to describe the line of the component
// 我们使用这个是因为我们实际上并不想描述组件的线路
// but just the component name.
// 而只是组件的名称。
const name = fn ? fn.displayName || fn.name : '';
return name ? describeBuiltInComponentFrame(name) : '';
}
export function getOwnerStackByFiberInDev(workInProgress: Fiber): string {
if (!__DEV__) {
return '';
}
try {
let info = '';
if (workInProgress.tag === HostText) {
// Text nodes never have an owner/stack because they're not created through JSX.
// 文本节点从不拥有 owner/stack,因为它们不是通过 JSX 创建的。
// We use the parent since text nodes are always created through a host parent.
// 我们使用父节点,因为文本节点总是通过宿主父节点创建的。
workInProgress = workInProgress.return as any;
}
// The owner stack of the current fiber will be where it was created, i.e. inside its owner.
// 当前 fiber 的拥有者堆栈将会是它被创建时的位置,也就是它的拥有者内部。
// There's no actual name of the currently executing component. Instead, that is available
// 当前正在执行的组件实际上没有具体的名称。不过,这个名称可以在当前正在执行的常规堆栈上获得。
// on the regular stack that's currently executing. However, for built-ins there is no such
// named stack frame and it would be ignored as being internal anyway. Therefore we add
// 然而,对于内建组件来说,没有这样的命名堆栈帧,而且无论如何它都会被视为内部而被忽略。
// add one extra frame just to describe the "current" built-in component by name.
// Similarly, if there is no owner at all, then there's no stack frame so we add the name
// 因此我们额外添加一个帧,仅用于用名称描述“当前”内建组件。
// of the root component to the stack to know which component is currently executing.
// 同样,如果根本没有拥有者,那么就没有堆栈帧,因此我们将根组件的名称添加到堆栈中,以便知道当前正在执行哪个组件。
switch (workInProgress.tag) {
case HostHoistable:
case HostSingleton:
case HostComponent:
info += describeBuiltInComponentFrame(workInProgress.type);
break;
case SuspenseComponent:
info += describeBuiltInComponentFrame('Suspense');
break;
case SuspenseListComponent:
info += describeBuiltInComponentFrame('SuspenseList');
break;
case ActivityComponent:
info += describeBuiltInComponentFrame('Activity');
break;
case ViewTransitionComponent:
if (enableViewTransition) {
info += describeBuiltInComponentFrame('ViewTransition');
break;
}
// Fallthrough
// 贯穿
case FunctionComponent:
case SimpleMemoComponent:
case ClassComponent:
if (!workInProgress._debugOwner && info === '') {
// Only if we have no other data about the callsite do we add
// the component name as the single stack frame.
// 只有在我们没有关于调用位置的其他数据时,才会将组件名称作为唯一的堆栈帧添加。
info += describeFunctionComponentFrameWithoutLineNumber(
workInProgress.type,
);
}
break;
case ForwardRef:
if (!workInProgress._debugOwner && info === '') {
info += describeFunctionComponentFrameWithoutLineNumber(
workInProgress.type.render,
);
}
break;
}
let owner: void | null | Fiber | ReactComponentInfo = workInProgress;
while (owner) {
if (typeof owner.tag === 'number') {
const fiber: Fiber = owner as any;
owner = fiber._debugOwner;
const debugStack = fiber._debugStack;
// If we don't actually print the stack if there is no owner of this JSX element.
// 如果这个 JSX 元素没有所有者,我们实际上不会打印堆栈。
// In a real app it's typically not useful since the root app is always controlled
// 在真实的应用中,这通常没有多大用处,因为根应用程序总是由框架控制的。
// by the framework. These also tend to have noisy stacks because they're not rooted
// 这些也往往会有冗杂的堆栈,因为它们不是基于 React 渲染,而是在一些命令式的启动代码中。
// in a React render but in some imperative bootstrapping code. It could be useful
// if the element was created in module scope. E.g. hoisted. We could add a a single
// 如果元素是在模块作用域中创建的,比如被提升了,这可能会有用。
// stack frame for context for example but it doesn't say much if that's a wrapper.
// 我们可以为上下文添加一个单独的堆栈帧,但如果那只是一个包装器,它也说明不了太多。
if (owner && debugStack) {
const formattedStack = formatOwnerStack(debugStack);
if (formattedStack !== '') {
info += '\n' + formattedStack;
}
}
} else if (owner.debugStack != null) {
// Server Component
const ownerStack: Error = owner.debugStack;
owner = owner.owner;
if (owner && ownerStack) {
// TODO: Should we stash this somewhere for caching purposes?
// 待办:我们是否应该将其存放在某处以进行缓存?
info += '\n' + formatOwnerStack(ownerStack);
}
} else {
break;
}
}
return info;
} catch (x) {
return '\nError generating stack: ' + x.message + '\n' + x.stack;
}
}
五、工具
1. Fiber 描述
信息
describeBuiltInComponentFrame 、 describeFunctionComponentFrame 、 describeClassComponentFrame 在 shared 中实现
function describeFiber(fiber: Fiber, childFiber: null | Fiber): string {
switch (fiber.tag) {
case HostHoistable:
case HostSingleton:
case HostComponent:
return describeBuiltInComponentFrame(fiber.type);
case LazyComponent:
// TODO: When we support Thenables as component types we should rename this.
// TODO: 当我们支持将 Thenables 作为组件类型时,我们应该重命名这个。
return describeBuiltInComponentFrame('Lazy');
case SuspenseComponent:
if (fiber.child !== childFiber && childFiber !== null) {
// If we came from the second Fiber then we're in the Suspense Fallback.
// 如果我们来自第二个 Fiber,那么我们就在 Suspense 回退中。
return describeBuiltInComponentFrame('Suspense Fallback');
}
return describeBuiltInComponentFrame('Suspense');
case SuspenseListComponent:
return describeBuiltInComponentFrame('SuspenseList');
case FunctionComponent:
case SimpleMemoComponent:
return describeFunctionComponentFrame(fiber.type);
case ForwardRef:
return describeFunctionComponentFrame(fiber.type.render);
case ClassComponent:
return describeClassComponentFrame(fiber.type);
case ActivityComponent:
return describeBuiltInComponentFrame('Activity');
case ViewTransitionComponent:
if (enableViewTransition) {
return describeBuiltInComponentFrame('ViewTransition');
}
// Fallthrough
// 贯穿
default:
return '';
}
}