让 50,000 项列表平滑渲染的简单技巧

发布: (2026年1月18日 GMT+8 08:22)
8 min read
原文: Dev.to

Source: Dev.to

为什么渲染 50,000 项会很糟糕?

这在数字如此夸张时可能显而易见,但如果是一个更小的数字,比如 1,000 呢?这个数字是任意的,并没有固定意义,因为它在不同设备及其资源条件下的影响程度不同。

DOM(文档对象模型)将网页的 HTML 结构表示为一个节点和对象的树。该树由浏览器的渲染引擎创建——我们不需要直接管理它。当 DOM 节点发生变化时,浏览器可能需要:

  1. 重新计算样式
  2. 重新布局页面
  3. 重新绘制页面的部分区域

涉及的节点越多,这一过程就越昂贵。

Source:

虚拟 DOM

虚拟 DOM 是对真实 DOM 的内存中表示,它 与浏览器的渲染引擎交互。当状态发生变化时,React 首先更新虚拟 DOM,而不是直接更新真实 DOM。随后它使用 diff(差分)算法来确定需要进行的更改。这种选择性的机制通过以下方式显著提升性能和可预测性:

  • 减少不必要的重新渲染
  • 最小化对真实 DOM 的直接操作

示例:一个 <select> 港口列表

<select>
  <option value="NLRTM">Rotterdam</option>
  <option value="DEHAM">Hamburg</option>
  <option value="FRLAV">Le Havre</option>
</select>

在虚拟 DOM 中(为便于说明而简化)的 “Rotterdam” 选项如下所示:

{
  type: 'option',
  props: {
    value: 'NLRTM',
    children: 'Rotterdam'
  },
  key: null,
  stateNode: HTMLOptionElement, // 对真实 DOM 节点的引用
  return: FiberNode,            // 指向父节点的指针
  sibling: FiberNode,           // 指向 “Hamburg” 的指针
  // ... 其他大量内部调和属性
}

正如你所见,React 为每个节点管理的元数据相当庞大。如果我们的 <select> 中有 1,000 个港口,React 的内存中就会充满 1,000 个此类对象。每次渲染该组件时,React 可能需要遍历一个庞大的链表对象,此外还有浏览器引擎创建的 JavaScript 与 C++ 对象。

虽然 1,000 项不会让现代机器崩溃,但它可能超出 16 ms 的帧预算,导致掉帧——尤其是在配合用户交互(如滚动或输入)时。移动设备的情况更糟,因为资源更为有限。在移动端滚动一个长的、未虚拟化的列表常常会感觉沉重,并产生明显的卡顿。

注意: 一个复杂的网页由许多组件组成,每个组件都有自己的开销。单独设计、开发和测试组件时,往往会掩盖这些累计成本。

为什么仅仅使用记忆化不足

大多数 React 开发者都会使用记忆化,这很容易理解:更少的重新渲染 听起来 像是工作量更小。然而,记忆化只能阻止 React 重新执行组件代码;它 并不会 减少浏览器需要绘制的工作量。如果页面上有 1,000 个元素,浏览器仍然必须对这 1,000 个元素进行布局和绘制。

在滚动等交互过程中,即使 React 不重新渲染,浏览器仍需在每一帧计算布局并绘制所有可见的元素。元素足够多时,仅这一步就足以导致卡顿。

渲染更少,而非更快

在理想情况下,你使用的 API 能够让你 分页过滤搜索,从而减少返回的结果数量。以港口选择器为例,API 本可以只返回所选国家的港口——比如 50 条选项,而不是返回全世界(甚至全欧洲)的所有港口。

但你并不总是能控制 API:

  • 内部 API 可能归其他团队所有。
  • 第三方 API 完全不在你的掌控之中。

在这些情况下,责任会转移到 UI 上。即使收到的是大数据集,也不必一次性渲染全部。渲染更少可能意味着:

  • 只显示屏幕上可见的内容。
  • 将工作延迟到用户交互时再执行。
  • 用搜索驱动的界面取代大型下拉列表。

目标不是让渲染更快,而是从一开始就避免不必要的工作。减少浏览器需要处理的元素数量,默认会让其他一切变得更省资源。

我们通过一种叫做 窗口化(也称为 虚拟化)的技术来实现这一点。

Source:

什么是虚拟化?

这个术语听起来比实际更吓人。虚拟化 意味着只渲染视口中可见的内容。视口可以是显示列表的任何元素——因此对于 <Select> 组件来说,它就是显示选项的可滚动容器。

如果视口显示六个元素,浏览器恰好渲染这六个元素,无论底层数据集有多大。我们并不是仅仅 隐藏 元素,而是 在它们离屏时将其从 DOM 中移除

Virtualization illustration

注意: 由于离屏项已从 DOM 中物理移除,原生浏览器功能(如“页面查找”)将无法检测到它们。此外,某些交互(例如期望所有选项都存在的键盘导航)可能需要额外处理。

要点

  • Big lists can cripple performance, especially on constrained devices.
  • Memoization helps with React’s work but does nothing for the browser’s layout/paint cost.
  • Virtualization/windowing removes off‑screen elements from the DOM, dramatically reducing the work the browser must do.

Use virtualization whenever you have large collections, and fall back to pagination or server‑side filtering when possible. This keeps your UI snappy and your users happy.

摘要

无论你是在构建聊天功能、社交动态,还是仅仅一个 Select 组件,虚拟化都能显著提升应用的流畅度。如果想尝试,建议使用成熟的库。针对 React,以下是可靠的选择:

如果你觉得本文有帮助并想支持更多此类内容,你可以:

封面图片来自 Edson JuniorUnsplash

Back to Blog

相关文章

阅读更多 »

React 是如何工作的?

Component 是 React 应用的基础,应用由 Component 组成。Component 只是一个返回 UI 的 JavaScript 函数。javascript function App { return Hello ; } JS...