React 组件栈帧
一、描述内置组件框架
let prefix;
let suffix;
export function describeBuiltInComponentFrame(name: string): string {
if (prefix === undefined) {
// Extract the VM specific prefix used by each line.
// 提取每行使用的虚拟机特定前缀。
try {
throw Error();
} catch (x) {
const match = x.stack.trim().match(/\n( *(at )?)/);
prefix = (match && match[1]) || '';
suffix =
x.stack.indexOf('\n at') > -1
? // V8
' (<anonymous>)'
: // JSC/Spidermonkey
x.stack.indexOf('@') > -1
? '@unknown:0:0'
: // Other
'';
}
}
// We use the prefix to ensure our stacks line up with native stack frames.
// 我们使用前缀来确保我们的栈与本地栈帧对齐。
return '\n' + prefix + name + suffix;
}
二、描述调试信息帧
export function describeDebugInfoFrame(
name: string,
env: ?string,
location: ?Error,
): string {
if (location != null) {
// If we have a location, it's the child's owner stack. Treat the bottom most frame as
// 如果我们有一个位置,它就是子对象的所有者堆栈。将最底层的帧视为该函数的位置。
// the location of this function.
const childStack = formatOwnerStack(location);
const idx = childStack.lastIndexOf('\n');
const lastLine = idx === -1 ? childStack : childStack.slice(idx + 1);
if (lastLine.indexOf(name) !== -1) {
// For async stacks it's possible we don't have the owner on it. As a precaution only
// 对于异步堆栈,我们可能没有它的所有者。作为预防,只有在该帧中包含函数名称时才使用它。
// use this frame if it has the name of the function in it.
return '\n' + lastLine;
}
}
return describeBuiltInComponentFrame(name + (env ? ' [' + env + ']' : ''));
}
三、描述原生组件框架
let reentry = false;
let componentFrameCache;
if (__DEV__) {
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
componentFrameCache = new PossiblyWeakMap<Function, string>();
}
/**
* Leverages native browser/VM stack frames to get proper details (e.g.
* * 利用原生浏览器/虚拟机堆栈帧来获取单个组件在组件堆栈中的详细信息(例如:
* filename, line + col number) for a single component in a component stack. We
* 文件名、行号、列号)。我们通过以下方式实现:
* do this by:
* (1) throwing and catching an error in the function - this will be our
* control error.
* (1) 在函数中抛出并捕获一个错误——这将作为我们的控制错误。
* (2) calling the component which will eventually throw an error that we'll
* catch - this will be our sample error.
* (2) 调用组件,该组件最终会抛出一个我们将捕获的错误——这将作为我们的示例错误。
* (3) diffing the control and sample error stacks to find the stack frame
* which represents our component.
* (3) 对比控制错误和示例错误堆栈,以找到表示我们组件的堆栈帧。
*/
export function describeNativeComponentFrame(
fn: Function,
construct: boolean,
): string {
// If something asked for a stack inside a fake render, it should get ignored.
// 如果在伪渲染中请求了一个堆栈,它应该被忽略。
if (!fn || reentry) {
return '';
}
if (__DEV__) {
const frame = componentFrameCache.get(fn);
if (frame !== undefined) {
return frame;
}
}
reentry = true;
const previousPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = DefaultPrepareStackTrace;
let previousDispatcher = null;
if (__DEV__) {
previousDispatcher = ReactSharedInternals.H;
// Set the dispatcher in DEV because this might be call in the render function
// 在开发环境中设置调度器,因为这可能会在渲染函数中调用警告。
// for warnings.
ReactSharedInternals.H = null;
disableLogs();
}
try {
/**
* Finding a common stack frame between sample and control errors can be
* 在样本错误和控制错误之间找到一个共同的堆栈帧可能会
* tricky given the different types and levels of stack trace truncation from
* 很棘手,因为不同的 JS 虚拟机对堆栈跟踪的截断类型和层级
* different JS VMs. So instead we'll attempt to control what that common
* 各不相同。因此,我们将尝试通过这个对象方法来控制那个共同帧应该是什么:
* frame should be through this object method:
* Having both the sample and control errors be in the function under the
* 让样本错误和控制错误都位于 `DescribeNativeComponentFrameRoot` 属性下
* `DescribeNativeComponentFrameRoot` property, + setting the `name` and
* 的函数中,设置函数的 `name` 和 `displayName` 属性
* `displayName` properties of the function ensures that a stack
* 可以确保在控制和样本堆栈中,
* frame exists that has the method name `DescribeNativeComponentFrameRoot` in
* 都存在一个包含方法名 `DescribeNativeComponentFrameRoot` 的堆栈帧。
* it for both control and sample stacks.
*/
const RunInRootFrame = {
DetermineComponentFrameRoot(): [?string, ?string] {
let control;
try {
// This should throw.
// 这应该会抛出异常。
if (construct) {
// Something should be setting the props in the constructor.
// 构造函数中应该有东西在设置 props。
const Fake = function () {
throw Error();
};
// $FlowFixMe[prop-missing]
Object.defineProperty(Fake.prototype, 'props', {
set: function () {
// We use a throwing setter instead of frozen or non-writable props
// 我们使用一个会抛出异常的 setter,而不是冻结或不可写的属性
// because that won't throw in a non-strict mode function.
// 因为在非严格模式函数中,那样不会抛出异常。
throw Error();
},
});
if (typeof Reflect === 'object' && Reflect.construct) {
// We construct a different control for this case to include any extra
// 我们为这种情况构建了一个不同的控件,以包含构造调用添加的任何额外帧。
// frames added by the construct call.
try {
Reflect.construct(Fake, []);
} catch (x) {
control = x;
}
Reflect.construct(fn, [], Fake);
} else {
try {
Fake.call();
} catch (x) {
control = x;
}
fn.call(Fake.prototype);
}
} else {
try {
throw Error();
} catch (x) {
control = x;
}
// TODO(luna): This will currently only throw if the function component
// tries to access React/ReactDOM/props. We should probably make this throw
// in simple components too
const maybePromise = fn();
// If the function component returns a promise, it's likely an async
// 如果函数组件返回一个 promise,它很可能是一个异步组件,
// component, which we don't yet support. Attach a noop catch handler to
// 而我们目前尚不支持。附加一个空操作的 catch 处理器以消除错误。
// silence the error.
// TODO: Implement component stacks for async client components?
// TODO: 为异步客户端组件实现组件栈?
if (maybePromise && typeof maybePromise.catch === 'function') {
maybePromise.catch(() => {});
}
}
} catch (sample) {
// This is inlined manually because closure doesn't do it for us.
// 这是手动内联的,因为 closure 不会为我们自动处理。
if (sample && control && typeof sample.stack === 'string') {
return [sample.stack, control.stack];
}
}
return [null, null];
},
};
RunInRootFrame.DetermineComponentFrameRoot.displayName =
'DetermineComponentFrameRoot';
const namePropDescriptor = Object.getOwnPropertyDescriptor(
RunInRootFrame.DetermineComponentFrameRoot,
'name',
);
// Before ES6, the `name` property was not configurable.
// 在 ES6 之前,`name` 属性是不可配置的。
if (namePropDescriptor && namePropDescriptor.configurable) {
// V8 utilizes a function's `name` property when generating a stack trace.
// V8 在生成堆栈跟踪时会使用函数的 `name` 属性。
Object.defineProperty(
RunInRootFrame.DetermineComponentFrameRoot,
// Configurable properties can be updated even if its writable descriptor
// 即使可写描述符设置为 `false`,可配置的属性仍然可以更新。
// is set to `false`.
'name',
{ value: 'DetermineComponentFrameRoot' },
);
}
const [sampleStack, controlStack] =
RunInRootFrame.DetermineComponentFrameRoot();
if (sampleStack && controlStack) {
// This extracts the first frame from the sample that isn't also in the control.
// 这会从样本中提取第一个不在控制中的帧。
// Skipping one frame that we assume is the frame that calls the two.
// 跳过一个我们假设调用这两个的帧。
const sampleLines = sampleStack.split('\n');
const controlLines = controlStack.split('\n');
let s = 0;
let c = 0;
while (
s < sampleLines.length &&
!sampleLines[s].includes('DetermineComponentFrameRoot')
) {
s++;
}
while (
c < controlLines.length &&
!controlLines[c].includes('DetermineComponentFrameRoot')
) {
c++;
}
// We couldn't find our intentionally injected common root frame, attempt
// 我们无法找到我们有意注入的公共根帧,尝试
// to find another common root frame by search from the bottom of the
// 从控制栈底部搜索另一个公共根帧...
// control stack...
if (s === sampleLines.length || c === controlLines.length) {
s = sampleLines.length - 1;
c = controlLines.length - 1;
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
// We expect at least one stack frame to be shared.
// 我们期望至少有一个堆栈帧是共享的。
// Typically this will be the root most one. However, stack frames may be
// 通常,这将是最根部的那个。然而,由于最大堆栈限制,堆栈帧可能会被截断。
// cut off due to maximum stack limits. In this case, one maybe cut off
// 在这种情况下,其中一个可能比另一个更早被截断。
// earlier than the other. We assume that the sample is longer or the same
// 我们假设样本的长度相同或更长,因此更早被截断。
// and there for cut off earlier. So we should find the root most frame in
// 所以我们应该在控制中找到样本中的最根部帧。
// the sample somewhere in the control.
c--;
}
}
for (; s >= 1 && c >= 0; s--, c--) {
// Next we find the first one that isn't the same which should be the
// frame that called our sample function and the control.
if (sampleLines[s] !== controlLines[c]) {
// In V8, the first line is describing the message but other VMs don't.
// 在 V8 中,第一行是在描述消息,但其他虚拟机不是。
// If we're about to return the first line, and the control is also on the same
// 如果我们即将返回第一行,并且控制也在同一行,
// line, that's a pretty good indicator that our sample threw at same line as
// 这通常是一个很好的指标,说明我们的样本在与控制相同的行抛出了异常。
// the control. I.e. before we entered the sample frame. So we ignore this result.
// 换句话说,在我们进入样本帧之前。因此我们会忽略这个结果。
// This can happen if you passed a class to function component, or non-function.
// 如果你将一个类传递给函数组件,或者传递了非函数,这种情况可能会发生。
if (s !== 1 || c !== 1) {
do {
s--;
c--;
// We may still have similar intermediate frames from the construct call.
// 我们可能仍然会有来自构造调用的类似中间帧。
// The next one that isn't the same should be our match though.
// 不过,下一个不相同的帧应该就是我们的匹配帧。
if (c < 0 || sampleLines[s] !== controlLines[c]) {
// V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
// V8 为原生类添加了一个“new”前缀。让我们把它去掉,使其更美观。
let frame = '\n' + sampleLines[s].replace(' at new ', ' at ');
// If our component frame is labeled "<anonymous>"
// 如果我们的组件框架被标记为"<anonymous>"
// but we have a user-provided "displayName"
// 但我们有用户提供的"displayName"
// splice it in to make the stack more readable.
// 将其插入,以使堆栈更易读。
if (fn.displayName && frame.includes('<anonymous>')) {
frame = frame.replace('<anonymous>', fn.displayName);
}
if (__DEV__) {
if (typeof fn === 'function') {
componentFrameCache.set(fn, frame);
}
}
// Return the line we found.
// 返回我们找到的行。
return frame;
}
} while (s >= 1 && c >= 0);
}
break;
}
}
}
} finally {
reentry = false;
if (__DEV__) {
ReactSharedInternals.H = previousDispatcher;
reenableLogs();
}
Error.prepareStackTrace = previousPrepareStackTrace;
}
// Fallback to just using the name if we couldn't make it throw.
// 如果无法让它抛出异常,则回退为仅使用名称。
const name = fn ? fn.displayName || fn.name : '';
const syntheticFrame = name ? describeBuiltInComponentFrame(name) : '';
if (__DEV__) {
if (typeof fn === 'function') {
componentFrameCache.set(fn, syntheticFrame);
}
}
return syntheticFrame;
}
四、描述类组件框架
export function describeClassComponentFrame(ctor: Function): string {
return describeNativeComponentFrame(ctor, true);
}
五、描述函数式组件
export function describeFunctionComponentFrame(fn: Function): string {
return describeNativeComponentFrame(fn, false);
}