React Fiber 性能追踪
一、作用
二、从 Lanes 设置当前堆栈
备注
getGroupNameOfHighestPriorityLane()由 ReactFiberLane#getGroupNameOfHighestPriorityLane 实现
export function setCurrentTrackFromLanes(lanes: Lanes): void {
currentTrack = getGroupNameOfHighestPriorityLane(lanes);
}
三、按顺序标记所有 Lane
export function markAllLanesInOrder() {
if (supportsUserTiming) {
// Ensure we create all tracks in priority order. Currently performance.mark() are in
// first insertion order but performance.measure() are in the reverse order. We can
// always add the 0 time slot even if it's in the past. That's still considered for
// ordering.
//
// 确保我们按优先顺序创建所有轨道。目前 performance.mark() 是按首次插入顺序执行的,
// 但 performance.measure() 是按相反顺序执行的。即使时间点为 0 且已经过去,我们也可以添加。
// 这仍然会考虑到排序。
console.timeStamp(
'Blocking Track',
0.003,
0.003,
'Blocking',
LANES_TRACK_GROUP,
'primary-light',
);
if (enableGestureTransition) {
console.timeStamp(
'Gesture Track',
0.003,
0.003,
'Gesture',
LANES_TRACK_GROUP,
'primary-light',
);
}
console.timeStamp(
'Transition Track',
0.003,
0.003,
'Transition',
LANES_TRACK_GROUP,
'primary-light',
);
console.timeStamp(
'Suspense Track',
0.003,
0.003,
'Suspense',
LANES_TRACK_GROUP,
'primary-light',
);
console.timeStamp(
'Idle Track',
0.003,
0.003,
'Idle',
LANES_TRACK_GROUP,
'primary-light',
);
}
}
四、记录组件
1. 记录组件挂载
export function logComponentMount(
fiber: Fiber,
startTime: number,
endTime: number,
): void {
logComponentTrigger(fiber, startTime, endTime, 'Mount');
}
2. 记录组件卸载
export function logComponentUnmount(
fiber: Fiber,
startTime: number,
endTime: number,
): void {
logComponentTrigger(fiber, startTime, endTime, 'Unmount');
}
3. 组件重新出现记录
export function logComponentReappeared(
fiber: Fiber,
startTime: number,
endTime: number,
): void {
logComponentTrigger(fiber, startTime, endTime, 'Reconnect');
}
4. 记录组件消失
export function logComponentDisappeared(
fiber: Fiber,
startTime: number,
endTime: number,
): void {
logComponentTrigger(fiber, startTime, endTime, 'Disconnect');
}
五、深度相等
1. 推送深度相同
export function pushDeepEquality(): boolean {
if (__DEV__) {
// If this is true then we don't reset it to false because we're tracking if any
// parent already warned about having deep equality props in this subtree.
//
// 如果这是 true,那么我们不会将其重置为 false,因为我们正在跟踪是否有任何父组件已经警告
// 过在此子树中存在深度相等属性。
return alreadyWarnedForDeepEquality;
}
return false;
}
2. 弹出深度相同
export function popDeepEquality(prev: boolean): void {
if (__DEV__) {
alreadyWarnedForDeepEquality = prev;
}
}
六、记录组件渲染
备注
getComponentNameFromFiber()由 getComponentNameFromFiber 实现addObjectDiffToProperties()由 shared 提供includesSomeLane()由 ReactFiberLane#includesSomeLane 实现
export function logComponentRender(
fiber: Fiber,
startTime: number,
endTime: number,
wasHydrated: boolean,
committedLanes: Lanes,
): void {
const name = getComponentNameFromFiber(fiber);
if (name === null) {
// Skip
// 跳过
return;
}
if (supportsUserTiming) {
const alternate = fiber.alternate;
let selfTime: number = fiber.actualDuration as any;
if (alternate === null || alternate.child !== fiber.child) {
for (let child = fiber.child; child !== null; child = child.sibling) {
selfTime -= child.actualDuration as any;
}
}
const color =
selfTime < 0.5
? wasHydrated
? 'tertiary-light'
: 'primary-light'
: selfTime < 10
? wasHydrated
? 'tertiary'
: 'primary'
: selfTime < 100
? wasHydrated
? 'tertiary-dark'
: 'primary-dark'
: 'error';
if (!__DEV__) {
console.timeStamp(
name,
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
color,
);
} else {
const props = fiber.memoizedProps;
const debugTask = fiber._debugTask;
if (
props !== null &&
alternate !== null &&
alternate.memoizedProps !== props
) {
// If this is an update, we'll diff the props and emit which ones changed.
// 如果这是一次更新,我们将比较属性的差异并触发已更改的属性。
const properties: Array<[string, string]> = [reusableChangedPropsEntry];
const isDeeplyEqual = addObjectDiffToProperties(
alternate.memoizedProps,
props,
properties,
0,
);
if (properties.length > 1) {
if (
isDeeplyEqual &&
!alreadyWarnedForDeepEquality &&
!includesSomeLane(alternate.lanes, committedLanes) &&
(fiber.actualDuration as any) > 100
) {
alreadyWarnedForDeepEquality = true;
// This is the first component in a subtree which rerendered with deeply equal props
// and didn't have its own work scheduled and took a non-trivial amount of time.
// We highlight this for further inspection.
// Note that we only consider this case if properties.length > 1 which it will only
// be if we have emitted any diffs. We'd only emit diffs if there were any nested
// equal objects. Therefore, we don't warn for simple shallow equality.
//
// 这是一个子树中的第一个组件,它在 props 深度相等的情况下重新渲染
// 并且自身没有安排任何工作,但耗时不少。
// 我们对其进行标记以便进一步检查。
// 注意,我们只考虑 properties.length > 1 的情况,这只有在我们发出了任何差异时才会发生。
// 我们只有在存在嵌套相等对象时才会发出差异。因此,对于简单的浅层相等,我们不会发出警告。
properties[0] = reusableDeeplyEqualPropsEntry;
reusableComponentDevToolDetails.color = 'warning';
reusableComponentDevToolDetails.tooltipText = DEEP_EQUALITY_WARNING;
} else {
reusableComponentDevToolDetails.color = color;
reusableComponentDevToolDetails.tooltipText = name;
}
reusableComponentDevToolDetails.properties = properties;
reusableComponentOptions.start = startTime;
reusableComponentOptions.end = endTime;
const measureName = '\u200b' + name;
if (debugTask != null) {
debugTask.run(
performance.measure.bind(
performance,
measureName,
reusableComponentOptions,
),
);
} else {
performance.measure(measureName, reusableComponentOptions);
}
performance.clearMeasures(measureName);
} else {
if (debugTask != null) {
debugTask.run(
console.timeStamp.bind(
console,
name,
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
color,
),
);
} else {
console.timeStamp(
name,
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
color,
);
}
}
} else {
if (debugTask != null) {
debugTask.run(
console.timeStamp.bind(
console,
name,
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
color,
),
);
} else {
console.timeStamp(
name,
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
color,
);
}
}
}
}
}
七、记录组件错误
备注
getComponentNameFromFiber()由 getComponentNameFromFiber 实现addValueToProperties()由 shared 提供
export function logComponentErrored(
fiber: Fiber,
startTime: number,
endTime: number,
errors: Array<CapturedValue<mixed>>,
): void {
if (supportsUserTiming) {
const name = getComponentNameFromFiber(fiber);
if (name === null) {
// Skip
// 跳过
return;
}
if (__DEV__) {
let debugTask: ?ConsoleTask = null;
const properties: Array<[string, string]> = [];
for (let i = 0; i < errors.length; i++) {
const capturedValue = errors[i];
if (debugTask == null && capturedValue.source !== null) {
// If the captured value has a source Fiber, use its debugTask for
// the stack instead of the error boundary's stack. So you can find
// which component errored since we don't show the errored render tree.
// TODO: Ideally we should instead, store the failed fibers and log the
// whole subtree including the component that errored.
//
// 如果捕获的值有源 Fiber,请使用其 debugTask 作为堆栈,而不是使用错误边界的堆栈。
// 这样你就可以找到出错的组件,因为我们不会显示出错的渲染树。
// 待办事项:理想情况下,我们应该存储失败的 Fiber 并记录整个子树,包括出错的组件。
debugTask = capturedValue.source._debugTask;
}
const error = capturedValue.value;
const message =
typeof error === 'object' &&
error !== null &&
typeof error.message === 'string'
? String(error.message)
: String(error);
properties.push(['Error', message]);
}
if (fiber.key !== null) {
addValueToProperties('key', fiber.key, properties, 0, '');
}
if (fiber.memoizedProps !== null) {
addObjectToProperties(fiber.memoizedProps, properties, 0, '');
}
if (debugTask == null) {
// If the captured values don't have a debug task, fallback to the
// error boundary itself.
//
// 如果捕获的值没有调试任务,则回退到错误边界本身。
debugTask = fiber._debugTask;
}
const options: PerformanceMeasureOptions = {
start: startTime,
end: endTime,
detail: {
devtools: {
color: 'error',
track: COMPONENTS_TRACK,
tooltipText:
fiber.tag === SuspenseComponent
? 'Hydration failed'
: 'Error boundary caught an error',
properties,
},
},
};
const measureName = '\u200b' + name;
if (__DEV__ && debugTask) {
debugTask.run(
performance.measure.bind(performance, measureName, options),
);
} else {
performance.measure(measureName, options);
}
performance.clearMeasures(measureName);
} else {
console.timeStamp(
name,
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
'error',
);
}
}
}
八、记录组件效果错误
备注
getComponentNameFromFiber()由 getComponentNameFromFiber 实现addValueToProperties()由 shared 提供
function logComponentEffectErrored(
fiber: Fiber,
startTime: number,
endTime: number,
errors: Array<CapturedValue<mixed>>,
): void {
if (supportsUserTiming) {
const name = getComponentNameFromFiber(fiber);
if (name === null) {
// Skip
// 跳过
return;
}
if (__DEV__) {
const properties: Array<[string, string]> = [];
for (let i = 0; i < errors.length; i++) {
const capturedValue = errors[i];
const error = capturedValue.value;
const message =
typeof error === 'object' &&
error !== null &&
typeof error.message === 'string'
? String(error.message)
: String(error);
properties.push(['Error', message]);
}
if (fiber.key !== null) {
addValueToProperties('key', fiber.key, properties, 0, '');
}
if (fiber.memoizedProps !== null) {
addObjectToProperties(fiber.memoizedProps, properties, 0, '');
}
const options = {
start: startTime,
end: endTime,
detail: {
devtools: {
color: 'error',
track: COMPONENTS_TRACK,
tooltipText: 'A lifecycle or effect errored',
properties,
},
},
};
const debugTask = fiber._debugTask;
const measureName = '\u200b' + name;
if (debugTask) {
debugTask.run(
performance.measure.bind(performance, measureName, options),
);
} else {
performance.measure(measureName, options);
}
performance.clearMeasures(measureName);
} else {
console.timeStamp(
name,
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
'error',
);
}
}
}
九、记录组件效果
备注
getComponentNameFromFiber()由 getComponentNameFromFiber 实现
export function logComponentEffect(
fiber: Fiber,
startTime: number,
endTime: number,
selfTime: number,
errors: null | Array<CapturedValue<mixed>>,
): void {
if (errors !== null) {
logComponentEffectErrored(fiber, startTime, endTime, errors);
return;
}
const name = getComponentNameFromFiber(fiber);
if (name === null) {
// Skip
// 跳过
return;
}
if (supportsUserTiming) {
const color =
selfTime < 1
? 'secondary-light'
: selfTime < 100
? 'secondary'
: selfTime < 500
? 'secondary-dark'
: 'error';
const debugTask = fiber._debugTask;
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
name,
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
color,
),
);
} else {
console.timeStamp(
name,
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
color,
);
}
}
}
十、记录生成时间
export function logYieldTime(startTime: number, endTime: number): void {
if (supportsUserTiming) {
const yieldDuration = endTime - startTime;
if (yieldDuration < 3) {
// Skip sub-millisecond yields. This happens all the time and is not interesting.
// 跳过亚毫秒级的让步。这种情况经常发生,并不有趣。
return;
}
// Being blocked on CPU is potentially bad so we color it by how long it took.
// 在 CPU 上被阻塞可能是不好的,所以我们根据它花费的时间来着色。
const color =
yieldDuration < 5
? 'primary-light'
: yieldDuration < 10
? 'primary'
: yieldDuration < 100
? 'primary-dark'
: 'error';
// This get logged in the components track if we don't commit which leaves them
// hanging by themselves without context. It's a useful indicator for why something
// might be starving this render though.
// TODO: Considering adding these to a queue and only logging them if we commit.
//
// 如果我们不提交,它会记录在组件的跟踪中,这会让它们独自挂起,没有上下文。虽然如此,这仍然是一个有用的指标,可以说明为什么某些渲染可能处于饥饿状态。
// TODO: 考虑将这些添加到队列中,只有在提交时才记录它们。
console.timeStamp(
'Blocked',
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
color,
);
}
}
十一、记录挂起生成时间
export function logSuspendedYieldTime(
startTime: number,
endTime: number,
suspendedFiber: Fiber,
): void {
if (supportsUserTiming) {
const debugTask = suspendedFiber._debugTask;
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
'Suspended',
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
'primary-light',
),
);
} else {
console.timeStamp(
'Suspended',
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
'primary-light',
);
}
}
}
十二、记录动作生成时间
export function logActionYieldTime(
startTime: number,
endTime: number,
suspendedFiber: Fiber,
): void {
if (supportsUserTiming) {
const debugTask = suspendedFiber._debugTask;
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
'Action',
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
'primary-light',
),
);
} else {
console.timeStamp(
'Action',
startTime,
endTime,
COMPONENTS_TRACK,
undefined,
'primary-light',
);
}
}
}
十三、日志阻塞开始
备注
includesOnlyHydrationOrOffscreenLanes()由 ReactFiberLane#includesOnlyHydrationOrOffscreenLanes 实现
export function logBlockingStart(
updateTime: number,
eventTime: number,
eventType: null | string,
eventIsRepeat: boolean,
isSpawnedUpdate: boolean,
isPingedUpdate: boolean,
renderStartTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask, // DEV-only
updateMethodName: null | string,
updateComponentName: null | string,
): void {
if (supportsUserTiming) {
currentTrack = 'Blocking';
// Clamp start times
if (updateTime > 0) {
if (updateTime > renderStartTime) {
updateTime = renderStartTime;
}
} else {
updateTime = renderStartTime;
}
if (eventTime > 0) {
if (eventTime > updateTime) {
eventTime = updateTime;
}
} else {
eventTime = updateTime;
}
// If a blocking update was spawned within render or an effect, that's considered a cascading render.
// If you have a second blocking update within the same event, that suggests multiple flushSync or
// setState in a microtask which is also considered a cascade.
//
// 如果在渲染或副作用中生成了阻塞更新,则这被视为级联渲染。
// 如果在同一个事件中你有第二个阻塞更新,这表明有多个 flushSync 或
// 微任务中的 setState,这也被视为级联。
if (eventType !== null && updateTime > eventTime) {
// Log the time from the event timeStamp until we called setState.
// 记录从事件的 timeStamp 到我们调用 setState 的时间
const color = eventIsRepeat ? 'secondary-light' : 'warning';
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType,
eventTime,
updateTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType,
eventTime,
updateTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
if (renderStartTime > updateTime) {
// Log the time from when we called setState until we started rendering.
// 记录从调用 setState 到开始渲染的时间。
const color = isSpawnedUpdate
? 'error'
: includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-light'
: 'primary-light';
const label = isPingedUpdate
? 'Promise Resolved'
: isSpawnedUpdate
? 'Cascading Update'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update';
if (__DEV__) {
const properties = [];
if (updateComponentName != null) {
properties.push(['Component name', updateComponentName]);
}
if (updateMethodName != null) {
properties.push(['Method name', updateMethodName]);
}
const measureOptions = {
start: updateTime,
end: renderStartTime,
detail: {
devtools: {
properties,
track: currentTrack,
trackGroup: LANES_TRACK_GROUP,
color,
},
},
};
if (enablePerformanceIssueReporting && isSpawnedUpdate) {
measureOptions.detail.devtools.performanceIssue =
reusableCascadingUpdateIssue;
}
if (debugTask) {
debugTask.run(
performance.measure.bind(performance, label, measureOptions),
);
} else {
performance.measure(label, measureOptions);
}
performance.clearMeasures(label);
} else {
console.timeStamp(
label,
updateTime,
renderStartTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
}
}
十四、记录手势开始
export function logGestureStart(
updateTime: number,
eventTime: number,
eventType: null | string,
eventIsRepeat: boolean,
isPingedUpdate: boolean,
renderStartTime: number,
debugTask: null | ConsoleTask, // DEV-only
updateMethodName: null | string,
updateComponentName: null | string,
): void {
if (supportsUserTiming) {
currentTrack = 'Gesture';
// Clamp start times
if (updateTime > 0) {
if (updateTime > renderStartTime) {
updateTime = renderStartTime;
}
} else {
updateTime = renderStartTime;
}
if (eventTime > 0) {
if (eventTime > updateTime) {
eventTime = updateTime;
}
} else {
eventTime = updateTime;
}
if (updateTime > eventTime && eventType !== null) {
// Log the time from the event timeStamp until we started a gesture.
// 记录从事件时间戳到我们开始手势的时间。
const color = eventIsRepeat ? 'secondary-light' : 'warning';
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType,
eventTime,
updateTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType,
eventTime,
updateTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
if (renderStartTime > updateTime) {
// Log the time from when we called setState until we started rendering.
// 记录从调用 setState 到开始渲染的时间。
const label = isPingedUpdate
? 'Promise Resolved'
: renderStartTime - updateTime > 5
? 'Gesture Blocked'
: 'Gesture';
if (__DEV__) {
const properties = [];
if (updateComponentName != null) {
properties.push(['Component name', updateComponentName]);
}
if (updateMethodName != null) {
properties.push(['Method name', updateMethodName]);
}
const measureOptions = {
start: updateTime,
end: renderStartTime,
detail: {
devtools: {
properties,
track: currentTrack,
trackGroup: LANES_TRACK_GROUP,
color: 'primary-light',
},
},
};
if (debugTask) {
debugTask.run(
performance.measure.bind(performance, label, measureOptions),
);
} else {
performance.measure(label, measureOptions);
}
performance.clearMeasures(label);
} else {
console.timeStamp(
label,
updateTime,
renderStartTime,
currentTrack,
LANES_TRACK_GROUP,
'primary-light',
);
}
}
}
}
十五、记录过渡开始
export function logTransitionStart(
startTime: number,
updateTime: number,
eventTime: number,
eventType: null | string,
eventIsRepeat: boolean,
isPingedUpdate: boolean,
renderStartTime: number,
debugTask: null | ConsoleTask, // DEV-only
updateMethodName: null | string,
updateComponentName: null | string,
): void {
if (supportsUserTiming) {
currentTrack = 'Transition';
// Clamp start times
// 固定开始时间
if (updateTime > 0) {
if (updateTime > renderStartTime) {
updateTime = renderStartTime;
}
} else {
updateTime = renderStartTime;
}
if (startTime > 0) {
if (startTime > updateTime) {
startTime = updateTime;
}
} else {
startTime = updateTime;
}
if (eventTime > 0) {
if (eventTime > startTime) {
eventTime = startTime;
}
} else {
eventTime = startTime;
}
if (startTime > eventTime && eventType !== null) {
// Log the time from the event timeStamp until we started a transition.
// 记录从事件的 timeStamp 到我们开始过渡的时间
const color = eventIsRepeat ? 'secondary-light' : 'warning';
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType,
eventTime,
startTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType,
eventTime,
startTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
if (updateTime > startTime) {
// Log the time from when we started an async transition until we called setState or started rendering.
// TODO: Ideally this would use the debugTask of the startTransition call perhaps.
//
// 记录从我们开始异步过渡到调用 setState 或开始渲染的时间。
// 待办:理想情况下,这应该使用 startTransition 调用的 debugTask。
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
'Action',
startTime,
updateTime,
currentTrack,
LANES_TRACK_GROUP,
'primary-dark',
),
);
} else {
console.timeStamp(
'Action',
startTime,
updateTime,
currentTrack,
LANES_TRACK_GROUP,
'primary-dark',
);
}
}
if (renderStartTime > updateTime) {
// Log the time from when we called setState until we started rendering.
// 记录从调用 setState 到开始渲染的时间。
const label = isPingedUpdate
? 'Promise Resolved'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update';
if (__DEV__) {
const properties = [];
if (updateComponentName != null) {
properties.push(['Component name', updateComponentName]);
}
if (updateMethodName != null) {
properties.push(['Method name', updateMethodName]);
}
const measureOptions = {
start: updateTime,
end: renderStartTime,
detail: {
devtools: {
properties,
track: currentTrack,
trackGroup: LANES_TRACK_GROUP,
color: 'primary-light',
},
},
};
if (debugTask) {
debugTask.run(
performance.measure.bind(performance, label, measureOptions),
);
} else {
performance.measure(label, measureOptions);
}
performance.clearMeasures(label);
} else {
console.timeStamp(
label,
updateTime,
renderStartTime,
currentTrack,
LANES_TRACK_GROUP,
'primary-light',
);
}
}
}
}
十六、日志渲染阶段
备注
includesOnlyHydrationOrOffscreenLanes()由 ReactFiberLane#includesOnlyHydrationOrOffscreenLanes 实现includesOnlyOffscreenLanes()由 ReactFiberLane#includesOnlyOffscreenLanes 实现includesOnlyHydrationLanes()由 ReactFiberLane#includesOnlyHydrationLanes 实现
export function logRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
const color = includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-dark'
: 'primary-dark';
const label = includesOnlyOffscreenLanes(lanes)
? 'Prepared'
: includesOnlyHydrationLanes(lanes)
? 'Hydrated'
: 'Render';
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
label,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
label,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
}
十七、记录中断的渲染阶段
备注
includesOnlyHydrationOrOffscreenLanes()由 ReactFiberLane#includesOnlyHydrationOrOffscreenLanes 实现includesOnlyOffscreenLanes()由 ReactFiberLane#includesOnlyOffscreenLanes 实现includesOnlyHydrationLanes()由 ReactFiberLane#includesOnlyHydrationLanes 实现
export function logInterruptedRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
const color = includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-dark'
: 'primary-dark';
const label = includesOnlyOffscreenLanes(lanes)
? 'Prewarm'
: includesOnlyHydrationLanes(lanes)
? 'Interrupted Hydration'
: 'Interrupted Render';
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
label,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
label,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
}
十八、已挂起
1. 记录已挂起的渲染阶段
备注
includesOnlyHydrationOrOffscreenLanes()由 ReactFiberLane#includesOnlyHydrationOrOffscreenLanes 实现
export function logSuspendedRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
const color = includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-dark'
: 'primary-dark';
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
'Prewarm',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
'Prewarm',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
}
2. 延迟阶段挂起日志
备注
includesOnlyHydrationOrOffscreenLanes()由 ReactFiberLane#includesOnlyHydrationOrOffscreenLanes 实现
export function logSuspendedWithDelayPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
// This means the render was suspended and cannot commit until it gets unblocked.
// 这意味着渲染已被暂停,必须等到解除阻塞后才能提交。
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
const color = includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-dark'
: 'primary-dark';
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
'Suspended',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
'Suspended',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
}
十九、记录恢复渲染阶段
export function logRecoveredRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
recoverableErrors: Array<CapturedValue<mixed>>,
hydrationFailed: boolean,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__) {
const properties: Array<[string, string]> = [];
for (let i = 0; i < recoverableErrors.length; i++) {
const capturedValue = recoverableErrors[i];
const error = capturedValue.value;
const message =
typeof error === 'object' &&
error !== null &&
typeof error.message === 'string'
? String(error.message)
: String(error);
properties.push(['Recoverable Error', message]);
}
const options: PerformanceMeasureOptions = {
start: startTime,
end: endTime,
detail: {
devtools: {
color: 'primary-dark',
track: currentTrack,
trackGroup: LANES_TRACK_GROUP,
tooltipText: hydrationFailed
? 'Hydration Failed'
: 'Recovered after Error',
properties,
},
},
};
if (debugTask) {
debugTask.run(
performance.measure.bind(performance, 'Recovered', options),
);
} else {
performance.measure('Recovered', options);
}
performance.clearMeasures('Recovered');
} else {
console.timeStamp(
'Recovered',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
);
}
}
}
二十、记录渲染阶段错误
export function logErroredRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
'Errored',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
),
);
} else {
console.timeStamp(
'Errored',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
);
}
}
}
廿一、记录不一致渲染
export function logInconsistentRender(
startTime: number,
endTime: number,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
'Teared Render',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
),
);
} else {
console.timeStamp(
'Teared Render',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
);
}
}
}
廿二、记录挂起提交阶段
export function logSuspendedCommitPhase(
startTime: number,
endTime: number,
reason: string,
debugTask: null | ConsoleTask,
): void {
// This means the commit was suspended on CSS or images.
// 这意味着该提交在 CSS 或图像上被暂停。
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
// TODO: Include the exact reason and URLs of what resources suspended.
// TODO: This might also be Suspended while waiting on a View Transition.
//
// 待办事项:包括被暂停的确切原因和资源的 URL。
// 待办事项:在等待视图切换时,这也可能被暂停。
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
reason,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
),
);
} else {
console.timeStamp(
reason,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
}
}
廿三、记录挂起的视图过渡阶段
export function logSuspendedViewTransitionPhase(
startTime: number,
endTime: number,
reason: string,
debugTask: null | ConsoleTask,
): void {
// This means the commit was suspended on CSS or images.
// 这意味着该提交在 CSS 或图像上被暂停。
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
// TODO: Include the exact reason and URLs of what resources suspended.
// TODO: This might also be Suspended while waiting on a View Transition.
//
// 待办事项:包括被暂停的确切原因和资源的 URL。
// 待办事项:在等待视图过渡时,这也可能被暂停。
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
reason,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
),
);
} else {
console.timeStamp(
reason,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
}
}
廿四、日志提交出错
export function logCommitErrored(
startTime: number,
endTime: number,
errors: Array<CapturedValue<mixed>>,
passive: boolean,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__) {
const properties: Array<[string, string]> = [];
for (let i = 0; i < errors.length; i++) {
const capturedValue = errors[i];
const error = capturedValue.value;
const message =
typeof error === 'object' &&
error !== null &&
typeof error.message === 'string'
? String(error.message)
: String(error);
properties.push(['Error', message]);
}
const options: PerformanceMeasureOptions = {
start: startTime,
end: endTime,
detail: {
devtools: {
color: 'error',
track: currentTrack,
trackGroup: LANES_TRACK_GROUP,
tooltipText: passive
? 'Remaining Effects Errored'
: 'Commit Errored',
properties,
},
},
};
if (debugTask) {
debugTask.run(
performance.measure.bind(performance, 'Errored', options),
);
} else {
performance.measure('Errored', options);
}
performance.clearMeasures('Errored');
} else {
console.timeStamp(
'Errored',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
);
}
}
}
廿五、记录提交阶段
export function logCommitPhase(
startTime: number,
endTime: number,
errors: null | Array<CapturedValue<mixed>>,
abortedViewTransition: boolean,
debugTask: null | ConsoleTask,
): void {
if (errors !== null) {
logCommitErrored(startTime, endTime, errors, false, debugTask);
return;
}
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
abortedViewTransition
? 'Commit Interrupted View Transition'
: 'Commit',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
abortedViewTransition ? 'error' : 'secondary-dark',
),
);
} else {
console.timeStamp(
abortedViewTransition ? 'Commit Interrupted View Transition' : 'Commit',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
abortedViewTransition ? 'error' : 'secondary-dark',
);
}
}
}
廿六、记录绘制生成阶段
export function logPaintYieldPhase(
startTime: number,
endTime: number,
delayedUntilPaint: boolean,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
delayedUntilPaint ? 'Waiting for Paint' : 'Waiting',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
),
);
} else {
console.timeStamp(
delayedUntilPaint ? 'Waiting for Paint' : 'Waiting',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
}
}
廿七、记录开始视图过渡生成阶段
export function logStartViewTransitionYieldPhase(
startTime: number,
endTime: number,
abortedViewTransition: boolean,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
abortedViewTransition
? 'Interrupted View Transition'
: 'Starting Animation',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
abortedViewTransition ? 'error' : 'secondary-light',
),
);
} else {
console.timeStamp(
abortedViewTransition
? 'Interrupted View Transition'
: 'Starting Animation',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
abortedViewTransition ? ' error' : 'secondary-light',
);
}
}
}
廿七、记录动画中阶段
export function logAnimatingPhase(
startTime: number,
endTime: number,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
'Animating',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
),
);
} else {
console.timeStamp(
'Animating',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
);
}
}
}
廿八、记录被动提交阶段
export function logPassiveCommitPhase(
startTime: number,
endTime: number,
errors: null | Array<CapturedValue<mixed>>,
debugTask: null | ConsoleTask,
): void {
if (errors !== null) {
logCommitErrored(startTime, endTime, errors, true, debugTask);
return;
}
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
console.timeStamp.bind(
console,
'Remaining Effects',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
),
);
} else {
console.timeStamp(
'Remaining Effects',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
);
}
}
}
廿九、常量
1. 支持用户计时
const supportsUserTiming =
enableProfilerTimer &&
typeof console !== 'undefined' &&
typeof console.timeStamp === 'function' &&
(!__DEV__ ||
// In DEV we also rely on performance.measure
// 在开发环境中,我们也依赖 performance.measure
(typeof performance !== 'undefined' &&
typeof performance.measure === 'function'));
2. 组件跟踪
const COMPONENTS_TRACK = 'Components ⚛';
3. 车道跟踪组
const LANES_TRACK_GROUP = 'Scheduler ⚛';
4. 可复用组件开发工具详情
const reusableComponentDevToolDetails = {
color: 'primary',
properties: (null: null | Array<[string, string]>),
tooltipText: '',
track: COMPONENTS_TRACK,
};
5. 可重用组件选项
const reusableComponentOptions: PerformanceMeasureOptions = {
start: -0,
end: -0,
detail: {
devtools: reusableComponentDevToolDetails,
},
};
6. 可重用已更改属性条目
const reusableChangedPropsEntry = ['Changed Props', ''];
7. 可重用级联更新问题
const reusableCascadingUpdateIssue = {
name: 'React: Cascading Update',
severity: 'warning',
description:
'A cascading update is an update that is triggered during an ongoing render. This can lead to performance issues.',
learnMoreUrl:
'https://react.dev/reference/dev-tools/react-performance-tracks#cascading-updates',
};
8. 深度相等警告
const DEEP_EQUALITY_WARNING =
'This component received deeply equal props. It might benefit from useMemo or the React Compiler in its owner.';
9. 可重用深度相等属性项
const reusableDeeplyEqualPropsEntry = ['Changed Props', DEEP_EQUALITY_WARNING];
三十、变量
1. 已因深度相等性而警告
let alreadyWarnedForDeepEquality = false;
卅一工具
1. 日志组件触发
function logComponentTrigger(
fiber: Fiber,
startTime: number,
endTime: number,
trigger: string,
) {
if (supportsUserTiming) {
reusableComponentOptions.start = startTime;
reusableComponentOptions.end = endTime;
reusableComponentDevToolDetails.color = 'warning';
reusableComponentDevToolDetails.tooltipText = trigger;
reusableComponentDevToolDetails.properties = null;
const debugTask = fiber._debugTask;
if (__DEV__ && debugTask) {
debugTask.run(
performance.measure.bind(
performance,
trigger,
reusableComponentOptions,
),
);
} else {
performance.measure(trigger, reusableComponentOptions);
}
performance.clearMeasures(trigger);
}
}