首页 / 文章

在 Chrome DevTools 中查看 React 性能跟踪数据,找出渲染缓慢的问题所在

了解 React 19.2 中新增的调度器、组件和服务器功能如何显示缓慢交互所耗费的时间,以及用于针对这些问题采取行动的“记录-修复-记录”工作流程。

发布者:REACTAPP.TOP阅读约 5 分钟1081 词

来源: Stop Guessing Why React Is Slow. Use the Performance Timeline.

当 React 的交互操作显得迟缓时,人们的本能反应是添加 useMemo、useCallback 和 React.memo,然后寄希望于问题能得到解决。但往往页面依然缓慢,因为没人去分析究竟是哪些因素消耗了时间。React 19.2 引入了性能追踪功能,它可以将 React 的操作记录到浏览器的性能时间轴中;下面将介绍每条追踪信息所显示的内容,以及如何通过这些记录找到问题的根源并加以解决。

为何普通的时间轴难以理解

在没有这些追踪功能的应用中,使用 Chrome DevTools 的性能面板记录一次缓慢的交互操作时,主线程的情况通常如下所示:

JavaScript
████████████████████
██████████
███
████████████

JavaScript显然处于繁忙状态,但到底是哪个组件在运行?是哪次更新触发了渲染?页面是否正在等待网络响应?是否有某个Effect占据了主导地位?React又是否在用户期待即时反馈时进行了渲染?常规的视图只能让你通过函数名来推测这些信息,而React的跟踪功能则提供了缺失的上下文。

React跟踪功能的作用

React为三个领域提供了专门的跟踪机制:

Scheduler
Components
Server

这些跟踪数据与浏览器原有的日志记录并列,因此可以大致这样读取单条追踪信息:

USER CLICK
    │
    ▼
┌─────────────────────────────┐
│ React Scheduler             │
│ Blocking → Render → Commit  │
├─────────────────────────────┤
│ React Components            │
│ Dashboard                   │
│ ├─ Header                   │
│ ├─ DataTable  ← expensive   │
│ └─ Chart                    │
├─────────────────────────────┤
│ Network / JavaScript        │
└─────────────────────────────┘

其价值在于关联性:React的运行状态与浏览器的运行状态都显示在同一个时间轴上。

调度器:为何这个任务现在才执行?

React 并不会以相同的方式处理每一次更新。调度器会根据优先级对任务进行分类,设有阻塞、过渡、挂起和空闲等通道,并为每次更新的各个阶段标注标签:更新、渲染、提交以及任何剩余的效应操作。

这使得诸如“为何在该字段输入时会出现延迟?”之类的问题能够得到解答。追踪信息可能会显示某次按键操作触发了阻塞通道中的复杂同步渲染过程;或者可能表明那些耗时的操作已经被正确地封装在过渡阶段中,从而指引你关注其他方面。无论哪种情况,你都是依据实际证据而非直觉来分析问题。如果你还不熟悉过渡阶段和优先级概念,Understanding React Lanes一文详细介绍了这些通道背后的原理。

组件:是谁完成了工作?

在日常调试中,这通常是最有用的追踪方式。它通过火焰图显示每个组件用于渲染和执行特效的时间,同时在开发过程中还能标出发生变化的属性以及级联更新。典型的结果如下:

ProductPage        310ms
├── Header           12ms
├── Filters          18ms
├── DataTable       245ms  ←
└── Footer            8ms

不必再模糊地抱怨 React 运行缓慢,现在有了明确的线索:DataTable 占用了大部分渲染时间。接下来可以检查它是否渲染了过多行、每次都会重新计算数据,或者每次都从父组件接收新属性。

服务器端:页面真的在等待 React 吗?

在使用 React Server Components 的应用中,开发版本还会在时间线上显示服务器请求及服务器组件的处理过程。这有助于避免常见的误判:

Slow page
   │
   ▼
Assume React render
   │
   X
   │
   ▼
Actually waiting on:
getProducts()
      │
      ▼
Database / API

如果跟踪数据显示页面处于空闲状态,而getProducts()却在等待数据库或API响应,那么解决方案应集中在数据获取环节,比如使用缓存、并行请求或优化查询语句;用React.memo包裹组件并无帮助。这正是为何需要在一个视图中查看整个执行时间线的原因。

可重复的调试工作流程

当发现交互速度过慢时,每次都按照相同的步骤操作:

1. Reproduce the problem
          ↓
2. Open Performance
          ↓
3. Record the interaction
          ↓
4. Find the slow React work
          ↓
5. Inspect the component / phase
          ↓
6. Check network and browser work
          ↓
7. Fix the actual bottleneck
          ↓
8. Record again

切勿跳过最后一步。在相同条件下再次进行记录并加以对比。未经重新测量的优化方案仅属于假设,有时第二次的跟踪数据会显示优化措施只是将问题转移到了其他地方,而非真正解决了它。

了解本地跟踪的局限性

性能跟踪功能是用于开发和性能分析的辅助工具。React指出,这些调试代码会增加运行开销,默认情况下在生产版本中会被关闭,而服务器端跟踪功能仅限于开发版本使用。此外,开发版本的运行速度整体也较慢,因此应更多参考比例数据而非绝对数值。

这意味着在您的机器上获得的完整跟踪数据并不能证明真实用户也能获得快速的使用体验。要了解生产环境中的性能表现,仍需依赖实际数据与真实用户的监控。这些跟踪功能能回答一个范围更窄但非常实用的问题:在您重现特定问题时,应用程序是按什么步骤运行的?

关键要点

最糟糕的性能优化习惯并非选择了错误的调试方式,而是在尚未理解问题之前就急于优化。更稳妥的做法应该是如下所示:

Slow interaction
       │
       ▼
    Measure
       │
       ▼
 Find the bottleneck
       │
 ┌─────┼──────┐
 ▼     ▼      ▼
React Network Browser
 │      │      │
 ▼      ▼      ▼
Fix    Fix    Fix
  • 先进行测量:跟踪功能使得记录和定位瓶颈变得简单。
  • 使用调度器跟踪来了解任务的优先级和执行阶段,再通过组件跟踪找出耗时较长的子树。
  • 在认定渲染环节存在问题之前,先检查服务器跟踪信息及网络通道状况。
  • 每次修改后都要重新录制,以确认问题已解决。
  • 将本地跟踪数据视为诊断工具,而用户实际体验则需依靠现场收集的数据。

这样表述后,讨论的重点就从是否添加useMemo转变为究竟是什么消耗了那200毫秒的时间,而正是这个问题才有助于找到真正的解决方案。