React 的 Virtual DOM 到底有多快?

che*_*tan 22 javascript dom angularjs reactjs react-native

我知道 React 的 Virtual DOM 更快有两个论据 -

  1. 它只更新那些实际需要更新的元素(使用 diff)。

  2. 它批量更新,因此我们只更新一次真实的 DOM。因此,重绘也只进行一次,否则会进行多次。

我对这两点都有疑问-

  1. 据我所知,所有现代浏览器的效率都足以只更新 DOM 中所需的元素。例如,如果我有两个“p”标签,并且我使用单击按钮更改了其中一个 p 标签中的文本,那么 safari 只会更新该 p 标签(我已经使用油漆闪烁验证了这一点)。那么,如果浏览器已经实现了第 1 点的优势呢?

  2. React 是如何批量更新更新的?最终 React 也将不得不使用 DOM api 来更新真正的 DOM。那么为什么如果我们直接使用 DOM api 则更改不会被批处理,而当 React 使用它时,它们会被批处理?

che*_*tan 19

我已经找到了我的问题的答案。

关键是要了解 Virtual DOM 的用途。

首先我们要看看 React 用什么方法来渲染组件。

不同的 javascript 框架采用不同的方法来检测数据模型中的更改并将它们呈现在视图上。

考虑 AngularJS。当我们在 Angular 模板中引用我们的数据时,例如在像 {{foo.x}} 这样的表达式中,Angular 不仅会呈现该数据,还会为该特定值创建一个观察者。每当我们的应用程序中发生任何事情(点击事件、HTTP 响应、超时)时,所有的观察者都会运行。如果观察者中的值已更改,则该值将在 UI 中重新呈现。通过运行所有观察者,AngularJS 本质上是在找出需要进行更改的位置。运行这些观察者的过程称为脏检查。

React 采用了不同的方法。每当 React 组件中的状态发生变化时,React 会从头开始重新渲染整个 UI(具有更新的状态),而不是找出在哪里进行更改(如 AngularJS)。

但是React的这种方式有问题。重新渲染整个 UI 意味着重新渲染整个 DOM 树。这是一个问题,因为 DOM 更新是一个缓慢的过程(由于回流和重绘)。

这就是 React 的 Virtual DOM 的用武之地。 Virtual DOM 只是 Real DOM 以 javascript 对象形式的表示。它只是存在于内存中的普通 javascript 对象的树数据结构。与 Real DOM 相比,Virtual DOM 的渲染速度要快得多,因为它永远不会在屏幕上渲染(不需要进行回流或重绘)。

那么Virtual DOM是如何解决这个问题的呢?当我们加载我们的应用程序时,React 创建一个 Virtual DOM,它是 Real DOM 的精确虚拟副本。每当组件中的状态发生变化时,React 不会重新渲染整个 Real DOM,而是渲染一个全新的 Virtual DOM(具有更新的状态)。然后它在旧的虚拟 DOM(真实 DOM 的初始副本)和这个新的虚拟 DOM(在状态改变后渲染)之间做一个差异,以找出它们之间的变化,它只做真实 DOM 中的那些变化。通过这种方式,整个 UI 被重新渲染(通过渲染一个全新的 Virtual DOM),但在 Real DOM 中只进行了最少的更改。

因此,当有人说“使用 Virtual DOM React 只更新那些需要更新的元素”(我的问题中的第 1 点)时,这意味着在 Virtual DOM React 的帮助下,React 正在克服其自身方法的局限性(方法从头开始渲染整个 UI)。

这个答案也解释了相同的概念。

我看到一些答案说使用 React 进行 DOM 操作比使用 DOM api 更快,因为 DOM api 重新渲染整个 DOM 树,而 React 仅重新渲染 DOM 树中需要更改的那些部分。这不是真的。所有现代浏览器都足够高效,只更新 DOM 树中需要更改的部分。这可以在浏览器的开发人员工具中使用油漆闪烁来验证(另请参阅此答案和此答案)。即使我们假设 DOM api 确实重新渲染了整个 DOM 树,这种推理仍然是错误的,因为 React 本身的内部代码必须使用 DOM api 来更新 DOM。如果 DOM api 确实重新渲染了整个 DOM 树,那么 React 也会重新渲染整个 DOM 树,因为最终它也会使用 DOM api 来更新 DOM。

 
至于第二点,React 实际上让我们更容易进行批处理。

在 React 中,虽然读取是在 Real DOM 上完成的,但写入(状态更改)不是在 Real DOM 上完成的。相反,写入是排队的。然后当我们所有的读取和写入都处理完毕后,一个新的虚拟 DOM 就会基于这些写入构建。然后在新旧虚拟 DOM 之间进行比较,然后 React 将所需的更改写入真实 DOM 以更新它。因此,最终 Real DOM 上的所有写入都在一次重排中一起完成。

但是我们也可以在没有 React 的情况下手动编写我们的代码,即首先完成所有读取,然后完成所有写入。React 使批处理更容易,因为使用 React 我们不必关心一起进行读取和写入,React 会自动为我们批处理写入。所以 React 不会让事情变快。它使事情变得更容易。

 
总之,我们可以说 React 实际上并没有更快。这更容易。正如 Pete Hunt 在本视频中所说,“React 不是魔术。就像您可以使用 C 进入汇编程序并击败 C 编译器一样,如果您愿意,您可以进入原始 DOM 操作和 DOM API 调用并击败 React。但是,使用 C 或 Java 或 JavaScript 是一个数量级的性能改进,因为您不必担心......平台的细节。使用 React,您可以构建应用程序,甚至无需考虑性能,并且默认状态很快。” .

Rich Harris 的这篇文章还指出,“虚拟 DOM 很快”是一个神话。

  • 我认为我的问题试图表达的观点(你承认有一定道理)就是 Pete Hunt 在视频中所说的 - “React 不是魔法。你总是可以编写汇编代码来击败 C。以类似的方式你总是可以使用原始 DOM 操作和 DOM api 来击败 React”(释义)。但 React 的目的从来都不是打败 Vanilla JS。React 的目的是将维护状态更改的工作从开发人员转移到库。 (2认同)
  • 不过,浏览器并不会真正“批量”更改,就像在这些框架中一样,DOM 对于浏览器来说仍然只是一个普通的 js 对象。它的作用是延迟*回流*直到下一次绘制(以显示器刷新率发生)。只有当 js 需要计算值时,它才会同步强制回流。请参阅/sf/ask/3313991131/#47343090 (2认同)
  • @anonymous_siva 在 React 中,setState 调用是异步的,并且它们是一起[批处理](https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous)。因此,您无需担心它们会被批量处理,因为 React 会为您做到这一点。 (2认同)

Dav*_*vid 5

在此输入图像描述

一旦 React 知道哪些虚拟 DOM 对象发生了变化,React 就只更新真实 DOM 中的那些对象。与直接操作真实 DOM 相比,这使得性能要好得多。这使得 React 作为高性能 JavaScript 库脱颖而出。

关于批量更新:

React遵循批量更新机制来更新真实的DOM。因此,导致性能提高。这意味着对真实 DOM 的更新是批量发送的,而不是针对每个状态更改发送更新。

UI 的重绘是最昂贵的部分,React 有效地确保真实 DOM 仅接收批量更新来重绘 UI。

  • “如何”让性能变得更好?即使没有 React,浏览器也只会更新那些需要更改的对象。React 带来了什么? (7认同)
  • 关于批量更新,react到底使用什么机制来“批量”更新。它如何使用 DOM api 批量发送更新? (3认同)
  • “然后 React 只更新真实 DOM 中的那些对象。与直接操作真实 DOM 相比,这使得性能要好得多。” 这不是在粗体文本的上下文中而是在仅更新已更改的对象的上下文中说的。 (2认同)