React 是异步渲染框架,传统的 DOMContentLoaded 无法准确捕获「真实渲染完成时间」,需:

  1. PerformanceObserver 监听浏览器原生指标(FP/FCP/LCP)作为基础;
  2. React 根组件挂载完成后,补充自定义计算(精准匹配业务首屏);
  3. 区分「白屏时间(FP/FCP)」和「首屏时间(LCP / 自定义首屏元素渲染)」。

完整监控代码(React 18 + 原生 API)

  1. 封装监控工具函数(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
/**
* React 白屏/首屏时间监控工具
* 兼容 React 18 并发渲染、TS/JS 项目
*/
export function monitorReactPerformance({
// 自定义首屏元素选择器(如首页的核心内容区)
firstScreenSelector = '#root > div',
// 数据上报回调
onReport = (data) => console.log('性能数据:', data)
} = {}) {
// 基础变量:页面加载起始时间(替代废弃的 performance.timing)
const pageStartTime = performance.now();
// 存储最终指标
const performanceData = {
whiteScreenTime: 0, // 白屏时间(FP/FCP)
firstScreenTime: 0, // 首屏时间(LCP/自定义)
fp: 0, // 首次绘制
fcp: 0, // 首次内容绘制
lcp: 0, // 最大内容绘制(首屏核心指标)
pageUrl: window.location.href,
timestamp: Date.now()
};

// ========== 1. 监听浏览器原生性能指标(FP/FCP/LCP) ==========
if ('PerformanceObserver' in window) {
// 监听 FP/FCP(白屏时间核心)
const paintObserver = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (entry.name === 'first-paint') {
performanceData.fp = entry.startTime;
performanceData.whiteScreenTime = entry.startTime; // FP 作为白屏时间兜底
}
if (entry.name === 'first-contentful-paint') {
performanceData.fcp = entry.startTime;
performanceData.whiteScreenTime = entry.startTime; // FCP 更贴近业务白屏
}
}
paintObserver.disconnect(); // 监听一次即可
});
paintObserver.observe({ entryTypes: ['paint'] });

// 监听 LCP(首屏时间核心,推荐)
const lcpObserver = new PerformanceObserver((entryList) => {
const lcpEntry = entryList.getEntries()[0];
if (lcpEntry) {
performanceData.lcp = lcpEntry.startTime;
performanceData.firstScreenTime = lcpEntry.startTime; // LCP 作为首屏时间
onReport({ ...performanceData }); // 拿到LCP立即上报
}
});
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
}

// ========== 2. React 挂载完成后计算自定义首屏时间(兜底) ==========
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) {
// 重试5次,每次间隔100ms
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;
// 若原生LCP未触发,更新白屏时间
if (!performanceData.whiteScreenTime) {
performanceData.whiteScreenTime = performanceData.firstScreenTime - 100; // 合理偏移
}
onReport({ ...performanceData });
return;
}
}
} catch (err) {
console.warn('检查首屏元素失败:', err);
}

// 异常兜底
performanceData.firstScreenTime = performance.now() - pageStartTime;
onReport({ ...performanceData });
}

// 确保React渲染完成后执行
setTimeout(checkElementRendered, 0); // 微任务队列执行,保证React已挂载
}

// ========== 3. 暴露给React根组件的挂载回调 ==========
return {
// React根组件挂载完成后调用此方法
onReactMounted: calculateCustomFirstScreen,
// 手动触发上报(备用)
report: () => onReport({ ...performanceData })
};
}
  1. 在 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';

// 1. 初始化监控
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));
}
});

// 2. 渲染React根组件,并在挂载完成后触发监控
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

// 3. React 挂载完成后计算自定义首屏时间(关键!)
performanceMonitor.onReactMounted();

验证与调试

启动项目后,打开 Chrome DevToolsPerformance → 勾选「Paint」,刷新页面可看到FP/FCP/LCP指标;
控制台会打印性能数据,可验证白屏 / 首屏时间是否符合预期;
首屏时间合理范围:移动端 < 1.5s,PC 端 < 1s,超过则需优化(如首屏骨架屏、代码分割、资源预加载)。

常见优化方向(监控后落地)

  • 白屏优化:
  1. 开启 React 服务端渲染(SSR)/ 静态生成(SSG);
  2. 首屏骨架屏,减少用户感知的白屏时间;
  3. 预加载核心 CSS/JSlink rel="preload")。
  • 首屏优化:
  1. 图片懒加载(loading="lazy");
  2. 代码分割(React.lazy + Suspense);
  3. 接口数据预请求,避免渲染后等待数据。