refactor: 使用 performanceMonitor 替换 useFirstScreenMetrics 中的 performance.now()

- useFirstScreenMetrics: 用 performanceMonitor.mark/measure 替换手动时间计算
- useSkeletonTiming: 用 usePerformanceMark Hook 重构,支持自定义前缀
- 所有性能数据统一到 performanceMonitor,可通过 generateReport() 查看

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-08 19:33:26 +08:00
parent 65f71603e1
commit a8c8fe4211

View File

@@ -16,6 +16,8 @@
import { useState, useEffect, useCallback, useRef } from 'react'; import { useState, useEffect, useCallback, useRef } from 'react';
import { initWebVitalsTracking, getCachedMetrics } from '@utils/performance/webVitals'; import { initWebVitalsTracking, getCachedMetrics } from '@utils/performance/webVitals';
import { collectResourceStats, collectApiStats } from '@utils/performance/resourceMonitor'; import { collectResourceStats, collectApiStats } from '@utils/performance/resourceMonitor';
import { performanceMonitor } from '@utils/performanceMonitor';
import { usePerformanceMark } from '@hooks/usePerformanceTracker';
import posthog from 'posthog-js'; import posthog from 'posthog-js';
import type { import type {
FirstScreenMetrics, FirstScreenMetrics,
@@ -44,11 +46,17 @@ export const useFirstScreenMetrics = (
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [metrics, setMetrics] = useState<FirstScreenMetrics | null>(null); const [metrics, setMetrics] = useState<FirstScreenMetrics | null>(null);
// 使用 ref 记录页面加载开始时间 // 使用 ref 避免重复标
const pageLoadStartRef = useRef<number>(performance.now()); const hasMarkedRef = useRef(false);
const skeletonStartRef = useRef<number>(performance.now());
const hasInitializedRef = useRef(false); const hasInitializedRef = useRef(false);
// 在组件首次渲染时标记开始时间点
if (!hasMarkedRef.current) {
hasMarkedRef.current = true;
performanceMonitor.mark(`${pageType}-page-load-start`);
performanceMonitor.mark(`${pageType}-skeleton-start`);
}
/** /**
* 收集所有首屏指标 * 收集所有首屏指标
*/ */
@@ -82,12 +90,20 @@ export const useFirstScreenMetrics = (
customProperties, customProperties,
}); });
// 5. 计算首屏可交互时间TTI // 5. 标记可交互时间点,并计算 TTI
const now = performance.now(); performanceMonitor.mark(`${pageType}-interactive`);
const timeToInteractive = now - pageLoadStartRef.current; const timeToInteractive = performanceMonitor.measure(
`${pageType}-page-load-start`,
`${pageType}-interactive`,
`${pageType} TTI`
) || 0;
// 6. 计算骨架屏展示时长 // 6. 计算骨架屏展示时长
const skeletonDisplayDuration = now - skeletonStartRef.current; const skeletonDisplayDuration = performanceMonitor.measure(
`${pageType}-skeleton-start`,
`${pageType}-interactive`,
`${pageType} 骨架屏时长`
) || 0;
const firstScreenMetrics: FirstScreenMetrics = { const firstScreenMetrics: FirstScreenMetrics = {
webVitals, webVitals,
@@ -143,9 +159,9 @@ export const useFirstScreenMetrics = (
const remeasure = useCallback(() => { const remeasure = useCallback(() => {
setIsLoading(true); setIsLoading(true);
// 重置计时器 // 重置性能标记
pageLoadStartRef.current = performance.now(); performanceMonitor.mark(`${pageType}-page-load-start`);
skeletonStartRef.current = performance.now(); performanceMonitor.mark(`${pageType}-skeleton-start`);
// 延迟收集指标(等待 Web Vitals 完成) // 延迟收集指标(等待 Web Vitals 完成)
setTimeout(() => { setTimeout(() => {
@@ -247,7 +263,7 @@ export const useFirstScreenMetrics = (
* *
* 使用示例: * 使用示例:
* ```tsx * ```tsx
* const { markSkeletonEnd } = useSkeletonTiming(); * const { markSkeletonEnd } = useSkeletonTiming('home-skeleton');
* *
* useEffect(() => { * useEffect(() => {
* if (!loading) { * if (!loading) {
@@ -256,27 +272,32 @@ export const useFirstScreenMetrics = (
* }, [loading, markSkeletonEnd]); * }, [loading, markSkeletonEnd]);
* ``` * ```
*/ */
export const useSkeletonTiming = () => { export const useSkeletonTiming = (prefix = 'skeleton') => {
const skeletonStartRef = useRef<number>(performance.now()); const { mark, getMeasure } = usePerformanceMark(prefix);
const skeletonEndRef = useRef<number | null>(null); const hasMarkedEndRef = useRef(false);
const hasMarkedStartRef = useRef(false);
// 在组件首次渲染时标记开始
if (!hasMarkedStartRef.current) {
hasMarkedStartRef.current = true;
mark('start');
}
const markSkeletonEnd = useCallback(() => { const markSkeletonEnd = useCallback(() => {
if (!skeletonEndRef.current) { if (!hasMarkedEndRef.current) {
skeletonEndRef.current = performance.now(); hasMarkedEndRef.current = true;
const duration = skeletonEndRef.current - skeletonStartRef.current; mark('end');
const duration = getMeasure('start', 'end');
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development' && duration) {
console.log(`⏱️ Skeleton Display Duration: ${(duration / 1000).toFixed(2)}s`); console.log(`⏱️ Skeleton Display Duration: ${(duration / 1000).toFixed(2)}s`);
} }
} }
}, []); }, [mark, getMeasure]);
const getSkeletonDuration = useCallback((): number | null => { const getSkeletonDuration = useCallback((): number | null => {
if (skeletonEndRef.current) { return getMeasure('start', 'end');
return skeletonEndRef.current - skeletonStartRef.current; }, [getMeasure]);
}
return null;
}, []);
return { return {
markSkeletonEnd, markSkeletonEnd,