我在 (Pete Hunt: React: Rethinking best practices -- JSConf EU 2013) 看到了一个 React 开发者演讲,演讲者提到模型的脏检查可能会很慢。但是,在大多数情况下,虚拟 DOM 应该比模型大,因此计算虚拟 DOM 之间的差异实际上不是性能更差吗?
我真的很喜欢 Virtual DOM 的潜在力量(尤其是服务器端渲染),但我想知道所有的优点和缺点。
我是 virtual-dom 模块的主要作者,所以我或许能够回答您的问题。实际上这里有2个问题需要解决
我什么时候重新渲染?答:当我观察到数据是脏的时候。如何有效地重新渲染?答:使用虚拟 DOM 生成真实 DOM 补丁
在 React 中,每个组件都有一个状态。这种状态就像你在淘汰赛或其他 MVVM 风格的库中可以找到的 observable。本质上,React 知道何时重新渲染场景,因为它能够观察到这些数据何时发生变化。脏检查比可观察对象慢,因为您必须定期轮询数据并递归检查数据结构中的所有值。相比之下,在状态上设置一个值将向侦听器发出某些状态已更改的信号,因此 React 可以简单地侦听状态上的更改事件并排队重新渲染。
虚拟 DOM 用于有效地重新渲染 DOM。这实际上与脏检查您的数据无关。您可以使用带或不带脏检查的虚拟 DOM 重新渲染。你是对的,计算两个虚拟树之间的差异有一些开销,但虚拟 DOM 差异是关于了解 DOM 中需要更新的内容,而不是你的数据是否发生了变化。实际上,diff 算法本身就是一个脏检查器,但它用于查看 DOM 是否脏。
我们的目标是仅在状态发生变化时重新渲染虚拟树。因此,使用 observable 检查状态是否已更改是防止不必要的重新渲染的有效方法,这会导致大量不必要的树差异。如果什么都没有改变,我们什么也不做。
虚拟 DOM 很好,因为它让我们可以像重新渲染整个场景一样编写代码。在幕后,我们想要计算一个补丁操作来更新 DOM 以符合我们的预期。因此,虽然虚拟 DOM diff/patch 算法可能不是最佳解决方案,但它为我们提供了一种非常好的方式来表达我们的应用程序。我们只需要准确地声明我们想要的内容,React/virtual-dom 就会计算出如何让你的场景看起来像这样。我们不必手动操作 DOM 或对以前的 DOM 状态感到困惑。我们也不必重新渲染整个场景,这可能比修补它的效率低得多。
我最近在这里阅读了一篇关于 React 的 diff 算法的详细文章:http://calendar.perfplanet.com/2013/diff/。据我了解,让 React 快速的原因是:
批处理 DOM 读/写操作。
仅有效更新子树。
与脏检查相比,IMO 的主要区别在于:
模型脏检查:每当调用 setState 时,React 组件都会显式设置为脏,因此这里不需要比较(数据)。对于脏检查,(模型的)比较总是在每个摘要循环中发生。 DOM 更新:DOM 操作非常昂贵,因为修改 DOM 也会应用和计算 CSS 样式、布局。从不必要的 DOM 修改中节省的时间可能比区分虚拟 DOM 所花费的时间更长。
第二点对于非平凡模型(例如具有大量字段或列表的模型)更为重要。复杂模型的一个字段更改将只导致涉及该字段的 DOM 元素所需的操作,而不是整个视图/模板。
$scope.$digest
在每个摘要周期执行多次,因此它是完整数据比较的多次而不是部分虚拟 DOM 树比较的一次。
我真的很喜欢 Virtual DOM 的潜在力量(尤其是服务器端渲染),但我想知道所有的优点和缺点。 -- 欧普
React 不是唯一的 DOM 操作库。我鼓励您通过阅读包含详细说明和基准的article from Auth0来了解替代方案。正如你所问的,我将在这里强调它们的优缺点:
React.js 的虚拟 DOM 优点 快速高效的“差异化”算法 多个前端(JSX、超脚本) 轻量级,可以在移动设备上运行 大量的牵引力和思想共享 可以在没有 React 的情况下使用(即作为独立引擎) 缺点 完全在内存中DOM 的副本(更高的内存使用) 静态元素和动态元素之间没有区别 Ember.js 的 Glimmer PROS 快速高效的差异算法 静态和动态元素之间的区别 100% 与 Ember 的 API 兼容(无需对现有元素进行重大更新即可获得好处代码)DOM 的轻量级内存表示 缺点 仅在 Ember 中使用 只有一个前端可用 增量 DOM 优点 减少内存使用 简单的 API 轻松与许多前端和框架集成(从一开始就作为模板引擎后端) 缺点 不是与其他库一样快(这是有争议的,请参阅下面的基准)更少的思想分享和社区使用
以下是 React 团队成员 Sebastian Markbåge 的评论,它提供了一些启示:
React 对输出进行区分(这是一种已知的可序列化格式,DOM 属性)。这意味着源数据可以是任何格式。它可以是不可变的数据结构和闭包内部的状态。 Angular 模型不保留引用透明度,因此本质上是可变的。您改变现有模型以跟踪更改。如果您的数据源每次都是不可变数据或新数据结构(例如 JSON 响应)怎么办?脏检查和 Object.observe 不适用于闭包范围状态。这两件事显然非常限制功能模式。此外,当您的模型复杂性增加时,进行脏跟踪变得越来越昂贵。然而,如果你只在可视化树上进行差异化,比如 React,那么它就不会增长太多,因为你能够在任何给定点在屏幕上显示的数据量都受到 UI 的限制。皮特上面的链接涵盖了更多的性能优势。
https://news.ycombinator.com/item?id=6937668
在 React 中,每个组件都有一个状态。这种状态就像你在淘汰赛或其他 MVVM 风格的库中可以找到的 observable。本质上,React 知道何时重新渲染场景,因为它能够观察到这些数据何时发生变化。脏检查比可观察对象慢,因为您必须定期轮询数据并递归检查数据结构中的所有值。相比之下,在状态上设置一个值将向侦听器发出某些状态已更改的信号,因此 React 可以简单地侦听状态上的更改事件并排队重新渲染。虚拟 DOM 用于有效地重新渲染DOM。这实际上与脏检查您的数据无关。您可以使用带或不带脏检查的虚拟 DOM 重新渲染。你是对的,计算两个虚拟树之间的差异有一些开销,但虚拟 DOM 差异是关于了解 DOM 中需要更新的内容,而不是你的数据是否发生了变化。实际上,diff 算法本身就是一个脏检查器,但它用于查看 DOM 是否脏。
我们的目标是仅在状态发生变化时重新渲染虚拟树。因此,使用 observable 检查状态是否已更改是防止不必要的重新渲染的有效方法,这会导致大量不必要的树差异。如果什么都没有改变,我们什么也不做。
Virtual Dom 不是由 react 发明的。它是 HTML dom 的一部分。它是轻量级的,并且与特定于浏览器的实现细节分离。
我们可以将虚拟 DOM 视为 React 的 HTML DOM 的本地和简化副本。它允许 React 在这个抽象世界中进行计算并跳过“真实”的 DOM 操作,这些操作通常很慢且特定于浏览器。实际上 DOM 和 VIRTUAL DOM 并没有太大的区别。
以下是使用 Virtual Dom 的要点(来源 Virtual DOM in ReactJS):
当你这样做时: document.getElementById('elementId').innerHTML = "New Value" 发生以下事情: 浏览器需要解析 HTML 它删除 elementId 的子元素 用新值更新 DOM 值 重新计算 css parent 和 child 更新布局,即每个元素在屏幕上的精确坐标遍历渲染树并将其绘制在浏览器显示上重新计算 CSS 和更改的布局使用复杂的算法,它们会影响性能。
以及更新 DOM 属性,即。价值观。它遵循一种算法。
现在,假设如果你直接更新 DOM 10 次,那么上面所有的步骤都会一个一个地运行,更新 DOM 算法需要时间来更新 DOM 值。
这就是为什么 Real DOM 比 virtual DOM 慢的原因。
不定期副业成功案例分享
unnecessary re-renders
的例子是什么?this.state.cats = 99
,您仍然需要脏检查来检查模型更改,就像 Angular 脏检查 $scope 树一样。这不是两种技术速度的比较,它只是声明 React 不做脏检查,因为它有一个 Backbone 样式设置器。