发布时间:2025 年 10 月 14 日
Interaction to Next Paint (INP) 是一项用于衡量响应速度的关键核心 Web 指标。在 Fotocasa,Google Search Console 突出显示了大量网页,这些网页在 2024 年 INP 取代首次输入延迟时间 (FID) 后,从“良好”变为“需要改进”和“较差”。此案例研究概述了用于诊断和解决这些问题的工具和策略,最终大幅提升了 INP。
Fotocasa 团队的起点
在从 FID 改为 INP 之前,桌面设备和移动设备上的几乎所有网页都达到了“良好”阈值,这意味着当时的所有核心网页指标(LCP、CLS 和 FID)都表现良好。不过,在改用 INP 后,几乎所有网页都改为了“需要改进”,有些甚至改为了“较差”,因为大多数用户互动的 INP 值始终超过 200 毫秒。
这一变化让 Fotocasa 团队意识到,他们忽略了用户体验的一个关键方面。虽然 FID 仅衡量首次互动的延迟时间,但 INP 会评估所有互动的响应能力,并考虑输入处理和呈现延迟时间。这种更广泛的衡量方式可以更好地反映真实的互动性(如 Google 所述),并突出显示错失的机会。
虽然 Google Search Console 提供实地效果数据,但它不提供实时数据洞见。其数据是在 28 天的时间范围内汇总的,因此很难准确找出当时导致问题的互动。
我们需要一种实时跟踪 INP 的方法,以便确定最慢且用户最常使用的互动,并在团队的开发环境中可靠地重现这些互动。同样重要的是,了解所做更改的影响,不仅要了解哪些修复有帮助,还要了解哪些调整无意中使情况变得更糟。
出于上述原因,我们使用了一组工具来帮助诊断和解决问题。其中最重要的有:
- Google Chrome 开发者工具,尤其是“性能”标签页。
- Fotocasa 团队在 Datadog 中使用 web-vitals 库构建的自定义 RUM(实时用户监控)系统。
- React 开发者工具。
用于诊断问题的工具
为了诊断和调试 INP 性能问题,我们使用了以下工具。
Google Chrome 开发者工具
检测和重现 Web 应用中与核心网页指标相关的问题的绝佳方法是使用 Google Chrome 开发者工具中的“性能”标签页。“性能”标签页会自动衡量核心 Web 指标,并即时反馈加载、互动性和布局偏移指标。它与这些指标在其他 Google 工具中的报告方式大致一致。
为了识别和解决 INP 问题,Fotocasa 团队通常会先限制 CPU,以模拟低端和中端设备的性能。这样,Fotocasa 团队就能观察网页在更受限的条件下的行为。然后,使用性能分析器记录会话,并仔细分析轨迹,重点关注用户互动,以找出性能问题。
在确定瓶颈时,检查 INP 子部分以及浏览器在每个子部分中执行的任务尤其有用。例如,在下图中,我们可以看到,由于文档正文中的样式发生变化,导致两次样式重新计算,因此 INP 相当高。
Fotocasa 设置了一个系统来跟踪 INP 和其他 Core Web Vital 指标,确保快速发现并解决任何性能问题。当某个指标超出特定阈值(基于 Google 定义的范围)时,系统会记录归因,以便分析和解决问题。
对于该系统,我们使用 web-vitals 库从真实用户那里捕获这些指标,捕获方式与 Chrome 衡量这些指标并将其报告给其他 Google 工具(例如 Chrome 用户体验报告、PageSpeed Insights、Search Console 的速度报告等)的方式完全一致。
为了获得全面的视图并实现集中式跟踪,Fotocasa 使用 Datadog 来收集和直观呈现数据,从而帮助团队做出明智的数据驱动型决策。自定义指标可确保成本效益,并能更好地跟踪 Fotocasa 网站上的几乎所有用户。
借助此系统,Fotocase 团队可以快速监控其修改是否会影响指标,或者是否会发生可能损害指标的意外更改。然后,可以将 INP 指标分解为输入延迟时间、处理时长和呈现延迟时间等部分,以精确定位交互的哪个部分是导致交互时间过长的主要原因。
在检测到异常情况(如图 7 和图 8 所示)后,Fotocasa 立即做出响应,并使用 OpenSearch(另一款有助于精确定位可能发生更改的位置的工具)。web-vitals 库提供的数据有助于识别目标(可能导致指标值偏高的 DOM 元素),并帮助团队更加专注于解决问题。
此外,还可以定义各种过滤条件,例如网页类型、设备或加载状态,以简化场景,并更准确地了解 INP 受到的影响。
React 开发者工具
React 开发者工具用于增强 Fotocasa 的调试功能,提供了一项强大的功能,可让您以直观方式突出显示已重新渲染的组件。
您可以前往 Profiler 标签页来启用此功能。然后,点击顶部栏右侧的齿轮,前往常规标签页,并选中在组件渲染时突出显示更新复选框。启用此功能后,组件在重新渲染时会突出显示,从而提供动态的直观表示。
找出重新渲染的原因
确定了重新渲染的组件后,下一个问题是“为什么会发生这种情况?”React 开发者工具会在火焰图视图中显示实用提示,从而解答此问题。
如需访问此信息,请记录分析器会话。通过分析性能分析器输出,您可以找到以下实用信息:
- 右上角会显示 React 提交的数量。
- 火焰图以直观方式呈现组件树,其中灰色表示未重新渲染的组件。每个条形图都表示 React 组件树发生变化以及相应变化提交到 DOM 的时刻。
- 将鼠标悬停在火焰图中的每个组件上,即可在“Why did this render?”子标题下查看其重新渲染的原因。
组件重新渲染的原因可能包括:
- 这是首次渲染组件
- 上下文已更改
- 钩子已更改
- 道具已更改
- “省份”已更改
- 父组件已渲染
确定渲染时间
火焰图中的颜色可传达有意义的信息。各种深浅不一的蓝色等颜色表示相应组件所需的渲染时间与其他组件相比相对较短。相反,橙色和红色表示相应组件的渲染时间较长。
Fotocasa 团队如何解决问题
移除不必要的重新渲染
每当 React 需要使用新数据更新界面时,就会发生重新渲染。这通常来自用户操作、API 响应或其他需要更新界面的重要事件。由于每次重新渲染都会运行 JavaScript,因此一次性进行过多的重新渲染(尤其是在大型组件树中)可能会阻塞主线程并导致性能问题。
重新渲染分为两种:
- 必要的重新渲染:当组件因拥有或使用新数据而确实需要更新时。
- 不必要的重新渲染:当组件在没有任何有意义的更改的情况下更新时,通常是由于状态管理效率低下或道具处理不当。
一些不必要的重新渲染通常不会造成太大问题,因为 React 的速度足够快,用户通常不会注意到。不过,如果此类重新渲染过于频繁或发生在大型组件树中,则可能会损害用户体验并对网页的 INP 产生负面影响。
Fotocasa 团队就是这样。他们发现,该网站存在许多不必要的重新渲染。这些情况主要出现在以下两种情况下:
- 网页加载期间:主线程上的长任务数量增加,首次互动延迟,这会对网页的 INP 产生负面影响。
- 在用户互动期间:增加了大多数互动的处理时间,这也损害了 INP。
Fotocasa 网站上许多不必要的重新渲染最终得到了优化。我们实现的最大优化之一是在“搜索”页面上。在网页加载时,出现了 3 次不必要的重新渲染。移除这些内容后,我们观察到以下结果:
- 长任务数量较少(见下图)
- Total Blocking Time 更短(比较图 14 和图 15)
状态同位
React 状态放置不当可能会降低应用的速度,并让用户感觉界面无响应。当组件的状态发生变化时,其子组件默认会重新渲染,除非使用转义机制(例如 memo)。
如上一部分中所述,重新渲染本身并不是坏事,但由于特定状态更新而重新渲染整个页面可能会导致互动速度变慢,因为 DOM 更新发生在渲染之后。
例如,在“搜索”页面上,当用户点击某个按钮时,系统会显示一个对话框,其中包含所有过滤条件。
在这种情况下,控制对话框打开状态的状态放置在搜索页面中。当此状态发生变化时,整个页面都会重新渲染,从而导致 INP 较差,尤其是在较慢的设备上(如在开发者工具中使用 CPU 节流时所见):
尽可能靠近触发更改的组件来更改状态,即可解决此问题。在这种特定情况下,可以将状态放置在过滤器的按钮组件中,这样当状态发生变化时,只会重新渲染按钮。
移除不必要的状态
状态不应包含冗余或重复信息。如果这样做,可能会导致不必要的重新渲染,并引发问题。
例如,在 Fotocasa 过滤条件栏中,会显示表示特定搜索应用了多少个过滤条件的文本:
已应用的过滤条件数量根据应用的状态计算得出。不过,这不仅导致整个组件不必要地重新渲染,在某些情况下还导致布局偏移,因为此组件是服务器端渲染的:
const [filtersCount, setFiltersCount] = useState(DEFAULT_COUNTER)
useEffect(() => {
const counter = filters
? Object.keys(filters)
?.reduce(reducerCounter, [])
?.filter((param) => searchParams?.[param]).length
: DEFAULT_COUNTER
setFiltersCount(counter)
}, [searchParams]);
为了解决此问题,该值是从过滤条件对象中派生出来的,使用的是变量而不是状态:
const counter = filters
? Object.keys(filters)
?.reduce(reducerCounter, [])
?.filter((param) => searchParams?.[param]).length
: DEFAULT_COUNTER;
减少昂贵的渲染
当 React 应用中发生互动时,通常会触发状态变化。如前所述,当组件的状态发生变化时,该组件及其所有子组件都会重新渲染。
如果其中一个组件的渲染函数运行缓慢,则可能会生成长时间运行的任务,导致 DOM 需要更长时间才能更新,从而对网页的 INP 产生负面影响。
Fotocasa 团队尝试尽可能减少组件渲染函数中耗时的计算。Chrome DevTools 和 React Developer Tools 在检测缓慢的渲染操作方面非常有用。
延迟代码执行
除了优化组件的渲染函数之外,还优化了其他函数,以尽可能减少长时间任务。不过,由于某些任务依赖于第三方代码,因此无法对其进行优化。
一个例子是分析。在这种情况下,我们决定延迟执行分析代码,并在发生用户互动时优先处理 DOM 更新。为了实现这一目标,Fotocasa 团队使用了一个名为 idlefy 的库,该库还可确保即使浏览器随后立即关闭,分析代码仍会运行。
绩效文化
性能工作并非一劳永逸,而是必须在每个功能发布到正式版时都考虑到的事项。团队中的每个人都需要保持一致,否则 Core Web Vitals 出现回归几乎是不可避免的。
为了及时掌握这些信息,Fotocasa 团队积极在团队内分享知识,并根据 Fotocasa 的 Datadog RUM 数据建立了一个清晰的框架来识别性能问题,包括如何重现这些问题。在 RUM 系统中设置了核心网页指标(尤其是 INP)的提醒,并配置为直接在 Slack 中通知 Fotocasa 团队。这种方法始终将性能放在首位,有助于在问题变成回归之前发现它们。
结果
为了改进 Fotocasa 的 INP,我们采取了技术优化和文化变革相结合的方式。通过消除不必要的重新渲染、优化状态放置、减少昂贵的渲染和延迟非关键代码,Fotocasa 团队成功将所有桌面版网页从“需要改进”升级为“良好”,并通过将几乎所有“较差”和“需要改进”的移动版网页升级为“良好”,显著改进了移动版网页。
这些变化提升了 Fotocasa 的整体用户体验,并与其他措施一起推动联系广告和电话潜在客户广告的转化次数增加了 27%,直接增强了公司的关键业务指标。
借助 Datadog 的实时监控功能,Fotocasa 团队能够验证 INP 改进效果、快速检测异常情况并防止出现回归。除了这些成就之外,Fotocasa 还成功将 Web 性能融入其开发文化中,确保 INP 和核心网页指标在每个版本中都保持优先地位。