React 是异步渲染框架,传统的 DOMContentLoaded 无法准确捕获「真实渲染完成时间」,需:
- 用
PerformanceObserver 监听浏览器原生指标(FP/FCP/LCP)作为基础;
- 在
React 根组件挂载完成后,补充自定义计算(精准匹配业务首屏);
- 区分「白屏时间(
FP/FCP)」和「首屏时间(LCP / 自定义首屏元素渲染)」。
完整监控代码(React 18 + 原生 API)
- 封装监控工具函数(
src/utils/performanceMonitor.js)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
|
export function monitorReactPerformance({ // 自定义首屏元素选择器(如首页的核心内容区) firstScreenSelector = '#root > div', // 数据上报回调 onReport = (data) => console.log('性能数据:', data) } = {}) { const pageStartTime = performance.now(); const performanceData = { whiteScreenTime: 0, firstScreenTime: 0, fp: 0, fcp: 0, lcp: 0, pageUrl: window.location.href, timestamp: Date.now() };
if ('PerformanceObserver' in window) { const paintObserver = new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { if (entry.name === 'first-paint') { performanceData.fp = entry.startTime; performanceData.whiteScreenTime = entry.startTime; } if (entry.name === 'first-contentful-paint') { performanceData.fcp = entry.startTime; performanceData.whiteScreenTime = entry.startTime; } } paintObserver.disconnect(); }); paintObserver.observe({ entryTypes: ['paint'] });
const lcpObserver = new PerformanceObserver((entryList) => { const lcpEntry = entryList.getEntries()[0]; if (lcpEntry) { performanceData.lcp = lcpEntry.startTime; performanceData.firstScreenTime = lcpEntry.startTime; onReport({ ...performanceData }); } }); lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] }); }
function calculateCustomFirstScreen() { function getFirstScreenElement() { const el = document.querySelector(firstScreenSelector); if (!el) return null; return el.nodeType === 1 ? el : null; }
function checkElementRendered() { const targetEl = getFirstScreenElement(); if (!targetEl) { const retryCount = checkElementRendered.retryCount || 0; if (retryCount < 5) { checkElementRendered.retryCount = retryCount + 1; setTimeout(checkElementRendered, 100); } else { performanceData.firstScreenTime = performance.now() - pageStartTime; onReport({ ...performanceData }); } return; }
try { if (typeof targetEl.getClientRects === 'function') { const rects = targetEl.getClientRects(); if (rects && rects.length > 0) { performanceData.firstScreenTime = performance.now() - pageStartTime; if (!performanceData.whiteScreenTime) { performanceData.whiteScreenTime = performanceData.firstScreenTime - 100; } onReport({ ...performanceData }); return; } } } catch (err) { console.warn('检查首屏元素失败:', err); }
performanceData.firstScreenTime = performance.now() - pageStartTime; onReport({ ...performanceData }); }
setTimeout(checkElementRendered, 0); }
return { onReactMounted: calculateCustomFirstScreen, report: () => onReport({ ...performanceData }) }; }
|
- 在 React 入口文件中集成(
src/index.jsx)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import { monitorReactPerformance } from './utils/performanceMonitor';
const performanceMonitor = monitorReactPerformance({ firstScreenSelector: '#root .home-content', onReport: (data) => { fetch('/api/performance/report', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).catch(err => console.error('性能数据上报失败:', err)); } });
const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />);
performanceMonitor.onReactMounted();
|
验证与调试
启动项目后,打开 Chrome DevTools → Performance → 勾选「Paint」,刷新页面可看到FP/FCP/LCP指标;
控制台会打印性能数据,可验证白屏 / 首屏时间是否符合预期;
首屏时间合理范围:移动端 < 1.5s,PC 端 < 1s,超过则需优化(如首屏骨架屏、代码分割、资源预加载)。
常见优化方向(监控后落地)
- 开启
React 服务端渲染(SSR)/ 静态生成(SSG);
- 首屏骨架屏,减少用户感知的白屏时间;
- 预加载核心
CSS/JS(link rel="preload")。
- 图片懒加载(
loading="lazy");
- 代码分割(
React.lazy + Suspense);
- 接口数据预请求,避免渲染后等待数据。