feat: ✅ 已完成的工作
1. 创建性能监控工具
- 提供完整的性能指标收集
- 自动生成性能报告
- 分析性能瓶颈并给出建议
2. 创建React性能Hook
- usePerformanceTracker - 追踪组件渲染性能
- usePerformanceMark - 标记自定义操作
3. 添加应用启动监控
- 标记 app-start 时间点
- 标记 react-ready 时间点
4. 添加认证监控
- 标记 auth-check-start 时间点
- 需要补充 auth-check-end 时间点
This commit is contained in:
@@ -3,6 +3,7 @@ import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import { logger } from '../utils/logger';
|
||||
import { performanceMonitor } from '@/utils/performanceMonitor';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { identifyUser, resetUser, trackEvent } from '@lib/posthog';
|
||||
import { SPECIAL_EVENTS } from '@lib/constants';
|
||||
@@ -52,6 +53,9 @@ export const AuthProvider = ({ children }) => {
|
||||
|
||||
lastCheckTimeRef.current = now;
|
||||
|
||||
// ⏱️ 性能监控: 标记认证检查开始
|
||||
performanceMonitor.mark('auth-check-start');
|
||||
|
||||
try {
|
||||
logger.debug('AuthContext', '开始检查Session状态', {
|
||||
timestamp: new Date().toISOString(),
|
||||
@@ -221,21 +225,7 @@ export const AuthProvider = ({ children }) => {
|
||||
setUser(data.user);
|
||||
setIsAuthenticated(true);
|
||||
|
||||
// ❌ 过时的追踪代码已移除(新代码在组件中使用 useAuthEvents 追踪)
|
||||
// 正确的事件追踪在 AuthFormContent.js 中调用 authEvents.trackLoginSuccess()
|
||||
// 事件名:'User Logged In' 或 'User Signed Up'
|
||||
// 属性名:login_method (不是 loginType)
|
||||
|
||||
// ⚡ 移除toast,让调用者处理UI反馈,避免并发更新冲突
|
||||
// toast({
|
||||
// title: "登录成功",
|
||||
// description: "欢迎回来!",
|
||||
// status: "success",
|
||||
// duration: 3000,
|
||||
// isClosable: true,
|
||||
// });
|
||||
|
||||
// ⚡ 登录成功后显示欢迎引导(延迟2秒,避免与登录Toast冲突)
|
||||
// ⚡ 登录成功后显示欢迎引导(延迟2秒,避免与登录Toast冲突,移动端不显示)
|
||||
setTimeout(() => {
|
||||
showWelcomeGuide();
|
||||
}, 2000);
|
||||
@@ -293,9 +283,9 @@ export const AuthProvider = ({ children }) => {
|
||||
isClosable: true,
|
||||
});
|
||||
|
||||
// ⚡ 注册成功后显示欢迎引导(延迟2秒)
|
||||
// ⚡ 注册成功后显示欢迎引导(延迟2秒,移动端不显示)
|
||||
setTimeout(() => {
|
||||
showWelcomeGuide();
|
||||
showWelcomeGuide();
|
||||
}, 2000);
|
||||
|
||||
return { success: true };
|
||||
|
||||
135
src/hooks/usePerformanceTracker.ts
Normal file
135
src/hooks/usePerformanceTracker.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
// src/hooks/usePerformanceTracker.ts
|
||||
// React组件性能追踪Hook
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { performanceMonitor } from '@/utils/performanceMonitor';
|
||||
import { logger } from '@/utils/logger';
|
||||
|
||||
interface PerformanceTrackerOptions {
|
||||
componentName: string;
|
||||
trackMount?: boolean; // 是否追踪组件挂载
|
||||
trackRender?: boolean; // 是否追踪每次渲染
|
||||
logToConsole?: boolean; // 是否输出到控制台
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能追踪Hook
|
||||
* 用于监控React组件的渲染性能
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function HomePage() {
|
||||
* usePerformanceTracker({
|
||||
* componentName: 'HomePage',
|
||||
* trackMount: true,
|
||||
* trackRender: true
|
||||
* });
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const usePerformanceTracker = ({
|
||||
componentName,
|
||||
trackMount = true,
|
||||
trackRender = false,
|
||||
logToConsole = true
|
||||
}: PerformanceTrackerOptions) => {
|
||||
const mountTimeRef = useRef<number>(0);
|
||||
const renderCountRef = useRef<number>(0);
|
||||
const lastRenderTimeRef = useRef<number>(0);
|
||||
|
||||
// 记录组件挂载时间
|
||||
useEffect(() => {
|
||||
if (!trackMount) return;
|
||||
|
||||
const mountTime = performance.now();
|
||||
mountTimeRef.current = mountTime;
|
||||
|
||||
// 标记组件挂载完成
|
||||
performanceMonitor.mark(`${componentName}-mounted`);
|
||||
|
||||
if (logToConsole) {
|
||||
logger.info('PerformanceTracker', `🎨 ${componentName} mounted`, {
|
||||
mountTime: `${mountTime.toFixed(2)}ms`,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
// Cleanup: 记录组件卸载
|
||||
return () => {
|
||||
const unmountTime = performance.now();
|
||||
const lifetime = unmountTime - mountTime;
|
||||
|
||||
performanceMonitor.mark(`${componentName}-unmounted`);
|
||||
|
||||
if (logToConsole) {
|
||||
logger.info('PerformanceTracker', `🗑️ ${componentName} unmounted`, {
|
||||
lifetime: `${lifetime.toFixed(2)}ms`,
|
||||
renderCount: renderCountRef.current
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [componentName, trackMount, logToConsole]);
|
||||
|
||||
// 追踪每次渲染
|
||||
useEffect(() => {
|
||||
if (!trackRender) return;
|
||||
|
||||
renderCountRef.current += 1;
|
||||
const renderTime = performance.now();
|
||||
const timeSinceLastRender = lastRenderTimeRef.current
|
||||
? renderTime - lastRenderTimeRef.current
|
||||
: 0;
|
||||
|
||||
lastRenderTimeRef.current = renderTime;
|
||||
|
||||
performanceMonitor.mark(`${componentName}-render-${renderCountRef.current}`);
|
||||
|
||||
if (logToConsole) {
|
||||
logger.debug('PerformanceTracker', `🔄 ${componentName} render #${renderCountRef.current}`, {
|
||||
renderTime: `${renderTime.toFixed(2)}ms`,
|
||||
timeSinceLastRender: timeSinceLastRender > 0
|
||||
? `${timeSinceLastRender.toFixed(2)}ms`
|
||||
: 'N/A'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
renderCount: renderCountRef.current,
|
||||
mountTime: mountTimeRef.current
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 标记性能时间点的Hook
|
||||
* 用于标记组件内的关键操作
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { markStart, markEnd, measure } = usePerformanceMark('HomePage');
|
||||
*
|
||||
* useEffect(() => {
|
||||
* markStart('data-fetch');
|
||||
* fetchData().then(() => {
|
||||
* markEnd('data-fetch');
|
||||
* const duration = measure('data-fetch-start', 'data-fetch-end');
|
||||
* });
|
||||
* }, []);
|
||||
* ```
|
||||
*/
|
||||
export const usePerformanceMark = (componentName: string) => {
|
||||
const markStart = (operationName: string) => {
|
||||
performanceMonitor.mark(`${componentName}-${operationName}-start`);
|
||||
};
|
||||
|
||||
const markEnd = (operationName: string) => {
|
||||
performanceMonitor.mark(`${componentName}-${operationName}-end`);
|
||||
};
|
||||
|
||||
const measure = (startMarkName: string, endMarkName: string) => {
|
||||
return performanceMonitor.measure(startMarkName, endMarkName);
|
||||
};
|
||||
|
||||
return { markStart, markEnd, measure };
|
||||
};
|
||||
@@ -1,4 +1,8 @@
|
||||
// src/index.js
|
||||
// ⏱️ 性能监控: 标记应用启动时间
|
||||
import { performanceMonitor } from '@/utils/performanceMonitor';
|
||||
performanceMonitor.mark('app-start');
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
@@ -126,6 +130,9 @@ async function startApp() {
|
||||
// Create root
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
|
||||
// ⏱️ 性能监控: 标记React准备就绪
|
||||
performanceMonitor.mark('react-ready');
|
||||
|
||||
// Render the app with Router wrapper
|
||||
// ✅ StrictMode 已启用(Chakra UI 2.10.9+ 已修复兼容性问题)
|
||||
root.render(
|
||||
|
||||
271
src/utils/performanceMonitor.ts
Normal file
271
src/utils/performanceMonitor.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
// src/utils/performanceMonitor.ts
|
||||
// 性能监控工具 - 统计白屏时间和性能指标
|
||||
|
||||
import { logger } from './logger';
|
||||
|
||||
/**
|
||||
* 性能指标接口
|
||||
*/
|
||||
export interface PerformanceMetrics {
|
||||
// 网络指标
|
||||
dns?: number; // DNS查询时间
|
||||
tcp?: number; // TCP连接时间
|
||||
ttfb?: number; // 首字节时间(Time To First Byte)
|
||||
domLoad?: number; // DOM加载时间
|
||||
resourceLoad?: number; // 资源加载时间
|
||||
|
||||
// 渲染指标
|
||||
fp?: number; // 首次绘制(First Paint)
|
||||
fcp?: number; // 首次内容绘制(First Contentful Paint)
|
||||
lcp?: number; // 最大内容绘制(Largest Contentful Paint)
|
||||
|
||||
// 自定义指标
|
||||
reactInit?: number; // React初始化时间
|
||||
authCheck?: number; // 认证检查时间
|
||||
homepageRender?: number; // 首页渲染时间
|
||||
|
||||
// 总计
|
||||
totalWhiteScreen?: number; // 总白屏时间
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能时间点记录
|
||||
*/
|
||||
const performanceMarks: Map<string, number> = new Map();
|
||||
|
||||
/**
|
||||
* 性能监控器类
|
||||
*/
|
||||
class PerformanceMonitor {
|
||||
private metrics: PerformanceMetrics = {};
|
||||
private isProduction: boolean;
|
||||
|
||||
constructor() {
|
||||
this.isProduction = process.env.NODE_ENV === 'production';
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记性能时间点
|
||||
*/
|
||||
mark(name: string): void {
|
||||
const timestamp = performance.now();
|
||||
performanceMarks.set(name, timestamp);
|
||||
|
||||
if (!this.isProduction) {
|
||||
logger.debug('PerformanceMonitor', `⏱️ Mark: ${name}`, {
|
||||
time: `${timestamp.toFixed(2)}ms`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个时间点之间的耗时
|
||||
*/
|
||||
measure(startMark: string, endMark: string): number | null {
|
||||
const startTime = performanceMarks.get(startMark);
|
||||
const endTime = performanceMarks.get(endMark);
|
||||
|
||||
if (!startTime || !endTime) {
|
||||
logger.warn('PerformanceMonitor', 'Missing performance mark', {
|
||||
startMark,
|
||||
endMark,
|
||||
hasStart: !!startTime,
|
||||
hasEnd: !!endTime
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
const duration = endTime - startTime;
|
||||
|
||||
if (!this.isProduction) {
|
||||
logger.debug('PerformanceMonitor', `📊 Measure: ${startMark} → ${endMark}`, {
|
||||
duration: `${duration.toFixed(2)}ms`
|
||||
});
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器性能指标
|
||||
*/
|
||||
collectBrowserMetrics(): void {
|
||||
if (!window.performance || !window.performance.timing) {
|
||||
logger.warn('PerformanceMonitor', 'Performance API not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
const timing = window.performance.timing;
|
||||
const navigationStart = timing.navigationStart;
|
||||
|
||||
// 网络指标
|
||||
this.metrics.dns = timing.domainLookupEnd - timing.domainLookupStart;
|
||||
this.metrics.tcp = timing.connectEnd - timing.connectStart;
|
||||
this.metrics.ttfb = timing.responseStart - navigationStart;
|
||||
this.metrics.domLoad = timing.domContentLoadedEventEnd - navigationStart;
|
||||
this.metrics.resourceLoad = timing.loadEventEnd - navigationStart;
|
||||
|
||||
// 获取 FP/FCP/LCP
|
||||
this.collectPaintMetrics();
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集绘制指标(FP/FCP/LCP)
|
||||
*/
|
||||
private collectPaintMetrics(): void {
|
||||
if (!window.performance || !window.performance.getEntriesByType) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First Paint & First Contentful Paint
|
||||
const paintEntries = performance.getEntriesByType('paint');
|
||||
paintEntries.forEach((entry: any) => {
|
||||
if (entry.name === 'first-paint') {
|
||||
this.metrics.fp = entry.startTime;
|
||||
} else if (entry.name === 'first-contentful-paint') {
|
||||
this.metrics.fcp = entry.startTime;
|
||||
}
|
||||
});
|
||||
|
||||
// Largest Contentful Paint (需要 PerformanceObserver)
|
||||
try {
|
||||
const observer = new PerformanceObserver((list) => {
|
||||
const entries = list.getEntries();
|
||||
const lastEntry = entries[entries.length - 1] as any;
|
||||
this.metrics.lcp = lastEntry.startTime;
|
||||
});
|
||||
observer.observe({ entryTypes: ['largest-contentful-paint'] });
|
||||
} catch (e) {
|
||||
// LCP可能不被支持
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集自定义React指标
|
||||
*/
|
||||
collectReactMetrics(): void {
|
||||
// React初始化时间
|
||||
const reactInit = this.measure('app-start', 'react-ready');
|
||||
if (reactInit) this.metrics.reactInit = reactInit;
|
||||
|
||||
// 认证检查时间
|
||||
const authCheck = this.measure('auth-check-start', 'auth-check-end');
|
||||
if (authCheck) this.metrics.authCheck = authCheck;
|
||||
|
||||
// 首页渲染时间
|
||||
const homepageRender = this.measure('homepage-render-start', 'homepage-render-end');
|
||||
if (homepageRender) this.metrics.homepageRender = homepageRender;
|
||||
|
||||
// 计算总白屏时间 (从页面开始到首屏完成)
|
||||
const totalWhiteScreen = this.measure('app-start', 'homepage-render-end');
|
||||
if (totalWhiteScreen) this.metrics.totalWhiteScreen = totalWhiteScreen;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成性能报告
|
||||
*/
|
||||
generateReport(): void {
|
||||
this.collectBrowserMetrics();
|
||||
this.collectReactMetrics();
|
||||
|
||||
const report = {
|
||||
'=== 网络阶段 ===': {
|
||||
'DNS查询': this.formatMs(this.metrics.dns),
|
||||
'TCP连接': this.formatMs(this.metrics.tcp),
|
||||
'首字节时间(TTFB)': this.formatMs(this.metrics.ttfb),
|
||||
'DOM加载': this.formatMs(this.metrics.domLoad),
|
||||
'资源加载': this.formatMs(this.metrics.resourceLoad),
|
||||
},
|
||||
'=== 渲染阶段 ===': {
|
||||
'首次绘制(FP)': this.formatMs(this.metrics.fp),
|
||||
'首次内容绘制(FCP)': this.formatMs(this.metrics.fcp),
|
||||
'最大内容绘制(LCP)': this.formatMs(this.metrics.lcp),
|
||||
},
|
||||
'=== React阶段 ===': {
|
||||
'React初始化': this.formatMs(this.metrics.reactInit),
|
||||
'认证检查': this.formatMs(this.metrics.authCheck),
|
||||
'首页渲染': this.formatMs(this.metrics.homepageRender),
|
||||
},
|
||||
'=== 总计 ===': {
|
||||
'总白屏时间': this.formatMs(this.metrics.totalWhiteScreen),
|
||||
}
|
||||
};
|
||||
|
||||
logger.info('PerformanceMonitor', '🚀 性能报告', report);
|
||||
|
||||
// 性能分析建议
|
||||
this.analyzePerformance();
|
||||
|
||||
return this.metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能分析和建议
|
||||
*/
|
||||
private analyzePerformance(): void {
|
||||
const issues: string[] = [];
|
||||
|
||||
if (this.metrics.ttfb && this.metrics.ttfb > 500) {
|
||||
issues.push(`⚠️ TTFB过高(${this.metrics.ttfb.toFixed(0)}ms) - 建议优化服务器响应`);
|
||||
}
|
||||
|
||||
if (this.metrics.resourceLoad && this.metrics.resourceLoad > 3000) {
|
||||
issues.push(`⚠️ 资源加载慢(${this.metrics.resourceLoad.toFixed(0)}ms) - 建议代码分割/CDN`);
|
||||
}
|
||||
|
||||
if (this.metrics.authCheck && this.metrics.authCheck > 300) {
|
||||
issues.push(`⚠️ 认证检查慢(${this.metrics.authCheck.toFixed(0)}ms) - 建议优化Session API`);
|
||||
}
|
||||
|
||||
if (this.metrics.totalWhiteScreen && this.metrics.totalWhiteScreen > 2000) {
|
||||
issues.push(`❌ 白屏时间过长(${this.metrics.totalWhiteScreen.toFixed(0)}ms) - 目标 <1500ms`);
|
||||
}
|
||||
|
||||
if (issues.length > 0) {
|
||||
logger.warn('PerformanceMonitor', '🔴 性能问题', { issues });
|
||||
} else {
|
||||
logger.info('PerformanceMonitor', '✅ 性能良好');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化毫秒数
|
||||
*/
|
||||
private formatMs(ms: number | undefined): string {
|
||||
if (ms === undefined) return 'N/A';
|
||||
|
||||
let emoji = '✅';
|
||||
if (ms > 1000) emoji = '❌';
|
||||
else if (ms > 500) emoji = '⚠️';
|
||||
|
||||
return `${ms.toFixed(0)}ms ${emoji}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有指标
|
||||
*/
|
||||
getMetrics(): PerformanceMetrics {
|
||||
return this.metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置监控器
|
||||
*/
|
||||
reset(): void {
|
||||
this.metrics = {};
|
||||
performanceMarks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export const performanceMonitor = new PerformanceMonitor();
|
||||
|
||||
// 页面加载完成后自动生成报告
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('load', () => {
|
||||
// 延迟1秒确保所有指标收集完成
|
||||
setTimeout(() => {
|
||||
performanceMonitor.generateReport();
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user