Profiler

Profiler 让你测量 React 应用的渲染性能,识别性能瓶颈。

核心概述

⚠️ 痛点: React 应用渲染性能难以测量

  • • 不知道哪些组件渲染频繁导致性能问题
  • • 难以确定状态更新导致的渲染开销
  • • 缺乏精确的性能分析工具
  • • React DevTools Profiler 需要手动操作,无法自动化

✅ 解决方案: Profiler 编程式性能测量

  • 精确测量: 记录每次渲染的时间和原因
  • 自动化测试: 可在单元测试中验证性能
  • 生产环境友好: 可选择性启用,不影响性能
  • 嵌套支持: 可同时测量多个组件

💡 心智模型: 性能监控摄像头

将 Profiler 想象成"性能监控摄像头":

  • 自动录制: 每次组件渲染时自动记录
  • 详细元数据: 记录渲染时间、原因、更新次数
  • 不影响主体: 轻量级监控,对性能影响极小
  • 回放分析: 通过回调函数分析渲染数据

技术规格

类型签名

<Profiler id="string" onRender={(id, phase, actualDuration, baseDuration, startTime, commitTime, interactions) => void}>
  {/* 子组件 */}
</Profiler>

interface OnRenderCallback {
  (
    id: string,                    // Profiler 的 id
    phase: 'mount' | 'update',     // mount 或 update
    actualDuration: number,        // 本次渲染耗时 (ms)
    baseDuration: number,          // 预估的渲染耗时 (ms)
    startTime: number,             // 渲染开始时间戳
    commitTime: number,            // 渲染完成时间戳
    interactions: Set<Interaction> // 触发渲染的交互集合
  ): void;
}

参数说明

参数类型说明
idstringProfiler 的唯一标识符,用于区分多个 Profiler
onRenderfunction渲染完成后调用的回调函数

onRender 回调参数

参数类型说明
idstringProfiler 的 id prop
phase'mount' | 'update''mount': 组件首次挂载
'update': 组件更新渲染
actualDurationnumber本次渲染实际耗时(毫秒)
baseDurationnumber预估的渲染耗时(毫秒)
startTimenumberReact 开始渲染的时间戳
commitTimenumberReact 提交更新到 DOM 的时间戳
interactionsSet<Interaction>触发本次渲染的交互集合(需要 trace API)

实战演练

示例 1: 基础性能测量

import { Profiler } from 'react';

function onRenderCallback(
  id: string,
  phase: 'mount' | 'update',
  actualDuration: number,
  baseDuration: number,
  startTime: number,
  commitTime: number
) {
  console.log({
    id,
    phase,
    actualDuration,
    baseDuration,
    startTime,
    commitTime,
  });
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <Navigation />
      <MainContent />
    </Profiler>
  );
}

// 控制台输出示例:
// {
//   id: "App",
//   phase: "mount",
//   actualDuration: 14.5,
//   baseDuration: 12.3,
//   startTime: 1678901234567,
//   commitTime: 1678901234582
// }

示例 2: 测量特定组件性能

import { Profiler, useState } from 'react';

function ExpensiveComponent({ data }: { data: number[] }) {
  // 模拟复杂计算
  const processed = data.map(n => n * 2);

  return (
    <div>
      {processed.map(n => (
        <div key={n}>{n}</div>
      ))}
    </div>
  );
}

function Dashboard() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>
        更新 ({count})
      </button>

      <Profiler
        id="ExpensiveComponent"
        onRender={(id, phase, actualDuration) => {
          if (actualDuration > 16) { // 超过一帧 (16ms)
            console.warn(`${id} 渲染耗时过长: ${actualDuration.toFixed(2)}ms`);
          }
        }}
      >
        <ExpensiveComponent data={Array.from({ length: 10000 }, (_, i) => i)} />
      </Profiler>
    </div>
  );
}

示例 3: 生产级性能监控

import { Profiler } from 'react';

// 性能数据收集
const performanceMetrics = new Map<string, number[]>();

function trackPerformance(
  id: string,
  phase: 'mount' | 'update',
  actualDuration: number
) {
  // 初始化数组
  if (!performanceMetrics.has(id)) {
    performanceMetrics.set(id, []);
  }

  // 记录本次渲染耗时
  const metrics = performanceMetrics.get(id)!;
  metrics.push(actualDuration);

  // 计算平均耗时
  const avgDuration = metrics.reduce((a, b) => a + b, 0) / metrics.length;

  // 发送到监控服务
  if (typeof window !== 'undefined' && window.gtag) {
    window.gtag('event', 'performance_render', {
      component_id: id,
      phase,
      duration: actualDuration,
      avg_duration: avgDuration,
    });
  }

  // 开发环境打印警告
  if (process.env.NODE_ENV === 'development' && actualDuration > 50) {
    console.warn(
      `[Performance] ${id} (${phase}) took ${actualDuration.toFixed(2)}ms (avg: ${avgDuration.toFixed(2)}ms)`
    );
  }
}

// 使用示例
function ProductList({ products }: { products: Product[] }) {
  return (
    <Profiler id="ProductList" onRender={trackPerformance}>
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </Profiler>
  );
}

// 定期报告性能统计
if (typeof window !== 'undefined') {
  setInterval(() => {
    const report = Array.from(performanceMetrics.entries()).map(
      ([id, durations]) => ({
        id,
        renders: durations.length,
        avg: durations.reduce((a, b) => a + b, 0) / durations.length,
        max: Math.max(...durations),
        min: Math.min(...durations),
      })
    );

    console.table(report);
  }, 30000); // 每30秒报告一次
}

避坑指南

❌ 错误 1: 在生产环境滥用 Profiler

// ❌ 错误: 所有组件都包装 Profiler
function App() {
  return (
    <Profiler id="App" onRender={callback}>
      <Profiler id="Header" onRender={callback}>
        <Header />
      </Profiler>
      <Profiler id="Sidebar" onRender={callback}>
        <Sidebar />
      </Profiler>
      <Profiler id="Content" onRender={callback}>
        <Content />
      </Profiler>
    </Profiler>
  );
}

// 问题:
// 1. Profiler 本身有性能开销
// 2. 过多的数据难以分析
// 3. 影响应用整体性能

✅ 正确 1: 有针对性地测量关键组件

// ✅ 正确: 只测量性能关键路径
function App() {
  return (
    <Fragment>
      <Header />
      <Sidebar />
      {/* 只测量可能慢的组件 */}
      <Profiler id="ProductList" onRender={callback}>
        <ProductList products={products} />
      </Profiler>
    </Fragment>
  );
}

// 优点:
// 1. 最小化性能开销
// 2. 数据清晰易分析
// 3. 只关注关键组件

❌ 错误 2: 在回调中执行重渲染操作

// ❌ 错误: 在 onRender 中 setState 导致无限循环
function BadExample() {
  const [renders, setRenders] = useState(0);

  const handleRender = () => {
    setRenders(r => r + 1); // 触发重新渲染!
  };

  return (
    <Profiler id="BadExample" onRender={handleRender}>
      <div>Renders: {renders}</div>
    </Profiler>
  );
}

// 问题:
// - onRender 调用 setState
// - setState 触发重新渲染
// - 重新渲染触发 onRender
// - 无限循环! 💥

✅ 正确 2: 回调函数保持纯净

// ✅ 正确: 只记录数据,不触发渲染
function GoodExample() {
  const handleRender = (
    id: string,
    phase: 'mount' | 'update',
    actualDuration: number
  ) => {
    // 只记录到外部系统
    analytics.track('component_render', {
      id,
      phase,
      duration: actualDuration,
    });

    // 或写入日志
    console.log(`${id} rendered in ${actualDuration}ms`);
  };

  return (
    <Profiler id="GoodExample" onRender={handleRender}>
      <div>Content</div>
    </Profiler>
  );
}

// 优点:
// - 不触发重渲染
// - 安全的性能监控
// - 可用于生产环境

❌ 错误 3: 忽略 Profiler 自身的性能开销

// ❌ 错误: 测量极快的组件
function FastComponent() {
  return <div>Hello</div>;
}

function App() {
  return (
    <Profiler id="FastComponent" onRender={callback}>
      <FastComponent />
    </Profiler>
  );
}

// 问题:
// - FastComponent 渲染只需 0.1ms
// - Profiler 开销可能超过 0.1ms
// - 测量结果不准确

✅ 正确 3: 只测量可能慢的组件

// ✅ 正确: 测量复杂组件
function SlowComponent({ items }: { items: Item[] }) {
  return (
    <div>
      {items.map(item => (
        <ComplexCard key={item.id} data={item} />
      ))}
    </div>
  );
}

function App() {
  return (
    <Fragment>
      {/* 不测量快组件 */}
      <FastComponent />

      {/* 只测量慢组件 */}
      <Profiler id="SlowComponent" onRender={callback}>
        <SlowComponent items={items} />
      </Profiler>
    </Fragment>
  );
}

// 优点:
// - Profiler 开销相对可忽略
// - 测量结果有意义
// - 聚焦性能问题

最佳实践

1. 开发 vs 生产环境

// 条件性启用 Profiler
const isDev = process.env.NODE_ENV === 'development';

function ProductionProfiler({
  id,
  children,
}: {
  id: string;
  children: React.ReactNode;
}) {
  if (isDev) {
    return (
      <Profiler id={id} onRender={devCallback}>
        {children}
      </Profiler>
    );
  }

  return <>{children}</>;
}

// 使用
<ProductionProfiler id="App">
  <App />
</ProductionProfiler>

2. 性能阈值告警

const PERFORMANCE_THRESHOLDS = {
  excellent: 16,  // 一帧 (60fps)
  good: 33,       // 两帧 (30fps)
  warning: 50,    // 需要优化
  critical: 100,  // 严重问题
};

function categorizePerformance(duration: number): string {
  if (duration < PERFORMANCE_THRESHOLDS.excellent) return '✅ Excellent';
  if (duration < PERFORMANCE_THRESHOLDS.good) return '🟢 Good';
  if (duration < PERFORMANCE_THRESHOLDS.warning) return '🟡 Warning';
  if (duration < PERFORMANCE_THRESHOLDS.critical) return '🟠 Slow';
  return '🔴 Critical';
}

function onRender(
  id: string,
  phase: 'mount' | 'update',
  actualDuration: number
) {
  const category = categorizePerformance(actualDuration);

  console.log(
    `[${id}] ${phase}: ${actualDuration.toFixed(2)}ms ${category}`
  );

  // 超过阈值发送告警
  if (actualDuration > PERFORMANCE_THRESHOLDS.warning) {
    alert(`Performance Warning: ${id} took ${actualDuration}ms`);
  }
}

3. 与 React DevTools 配合

特性Profiler APIReact DevTools
使用场景生产环境、自动化测试开发环境、手动调试
性能开销较小几乎无
数据持久化可编程控制需要手动导出
实时性每次渲染实时记录需要手动开始/停止

4. 性能优化流程

// 1. 识别性能问题
const slowComponents = new Set<string>();

// 2. 添加 Profiler
<Profiler
  id="ComponentName"
  onRender={(id, phase, actualDuration) => {
    if (actualDuration > 16) {
      slowComponents.add(id);
    }
  }}
>
  <ComponentName />
</Profiler>

// 3. 分析数据
const report = Array.from(slowComponents).map(id => ({
  id,
  suggestions: getSuggestions(id),
}));

// 4. 应用优化
function OptimizedComponent({ data }) {
  // 使用 React.memo 避免不必要的重渲染
  // 使用 useMemo 缓存计算结果
  // 使用 useCallback 缓存函数
  // 拆分组件减少渲染范围
}

// 5. 验证优化效果
<Profiler id="OptimizedComponent" onRender={verifyCallback}>
  <OptimizedComponent />
</Profiler>

相关链接