页面性能监听react hooks

import { useEffect, useRef } from 'react';

interface LayoutShift extends PerformanceEntry {

value: number;

hadRecentInput: boolean;

}

interface Metric {

name: string;

value: number;

unit: string;

level: string;

}

export const usePerformance = (enable = import.meta.env.DEV) => {

const started = useRef(false);

useEffect(() => {

if (!enable || started.current) return;

started.current = true;

const observers: PerformanceObserver\[\] = \[\];

const metrics = new Map<string, number>();

const addMetric = (name: string, value: number) => {

metrics.set(name, Number(value.toFixed(2)));

};

// ---------------- Navigation ----------------

const nav = performance.getEntriesByType(

'navigation'

)0 as PerformanceNavigationTiming;

if (nav) {

addMetric('TTFB', nav.responseStart);

addMetric('DOM Ready', nav.domContentLoadedEventEnd);

addMetric('Load', nav.loadEventEnd);

}

// ---------------- Paint ----------------

performance

.getEntriesByType('paint')

.forEach((entry: PerformanceEntry) => {

if (entry.name === 'first-paint') {

addMetric('FP', entry.startTime);

addMetric('白屏时间', entry.startTime);

}

if (entry.name === 'first-contentful-paint') {

addMetric('FCP', entry.startTime);

}

});

// ---------------- LCP ----------------

const lcpObserver = new PerformanceObserver((list) => {

const last = list.getEntries().at(-1);

if (last) {

addMetric('LCP', last.startTime);

}

});

lcpObserver.observe({

type: 'largest-contentful-paint',

buffered: true,

});

observers.push(lcpObserver);

// ---------------- CLS ----------------

let cls = 0;

const clsObserver = new PerformanceObserver((list) => {

(list.getEntries() as LayoutShift\[\]).forEach((entry) => {

if (!entry.hadRecentInput) {

cls += entry.value;

}

});

metrics.set('CLS', Number(cls.toFixed(3)));

});

clsObserver.observe({

type: 'layout-shift',

buffered: true,

});

observers.push(clsObserver);

// ---------------- INP ----------------

if (

PerformanceObserver.supportedEntryTypes.includes('event')

) {

const inpObserver = new PerformanceObserver((list) => {

const max = Math.max(

...list.getEntries().map((e) => e.duration)

);

addMetric('INP', max);

});

inpObserver.observe({

type: 'event',

buffered: true,

durationThreshold: 40,

});

observers.push(inpObserver);

}

// ---------------- 输出 ----------------

const timer = window.setTimeout(() => {

const getLevel = (name: string, value: number) => {

switch (name) {

case 'FCP':

return value < 1800 ? '🟢' : value < 3000 ? '🟡' : '🔴';

case 'LCP':

return value < 2500 ? '🟢' : value < 4000 ? '🟡' : '🔴';

case 'CLS':

return value < 0.1 ? '🟢' : value < 0.25 ? '🟡' : '🔴';

case 'INP':

return value < 200 ? '🟢' : value < 500 ? '🟡' : '🔴';

default:

return value < 1000 ? '🟢' : value < 3000 ? '🟡' : '🔴';

}

};

const table: Metric\[\] = \[\];

metrics.forEach((value, key) => {

table.push({

name: key,

value,

unit: key === 'CLS' ? '' : 'ms',

level: getLevel(key, value),

});

});

console.groupCollapsed(

'%c🚀 页面性能',

'background:#1677ff;color:white;padding:4px 10px;border-radius:4px'

);

console.table(table);

console.groupEnd();

}, 2500);

return () => {

window.clearTimeout(timer);

observers.forEach((o) => o.disconnect());

started.current = false;

};

}, enable);

};