我了解React有两个更快的Virtual DOM参数-
它仅更新那些实际需要更新的元素(使用diff)。
它分批进行更新,因此我们仅一次更新实际DOM。因此,重新粉刷也只完成一次,否则将需要进行多次。
我对这两点都有疑问-
据我所知,所有现代浏览器的效率足以更新DOM中所需的元素。例如,如果我有两个'p'标签,并且我通过单击按钮更改了p标签之一中的文本,则只有Safari浏览器会更新该p标签(我已经使用涂漆闪烁功能对此进行了验证)。那么,如果第1点已经被浏览器实现,那么它的优势是什么呢?
React如何精确地批量更新?最终,React也将不得不使用DOM API来更新真实的DOM。那为什么为什么如果我们直接使用DOM api,那么更改将不会被批处理,而当React使用它时,它们将被批处理?
我已经找到问题的答案。
关键是要了解虚拟DOM的目的。
首先,我们必须看一下React使用什么方法来渲染组件。
不同的javascript框架采用不同的方法来检测数据模型中的更改并将其呈现在视图上。
考虑AngularJS。当我们在Angular模板中(例如在类似{{foo.x}}的表达式中)引用数据时,Angular不仅会渲染该数据,还会为该特定值创建一个监视程序。每当我们的应用程序中发生任何事情(单击事件,HTTP响应,超时)时,所有观察程序都将运行。如果观察程序中的值已更改,则将在UI中重新呈现该值。通过运行所有观察程序,AngularJS实质上可以找到需要进行更改的位置。运行这些观察程序的过程称为脏检查。
反应采用不同的方法。每当React组件中的状态发生变化时,React无需找出更改的位置(例如AngularJS),而是从头开始重新呈现该组件的整个UI(具有更新的状态)。
但是React的这种方法有问题。重新呈现该组件的整个UI意味着重新呈现该组件的整个DOM。这是一个问题,因为DOM更新是一个缓慢的过程(由于重排和重绘)。
这是React的Virtual DOM的来源。React以javascript对象的形式存储Real DOM的副本。该副本称为虚拟DOM。与真实DOM相比,此虚拟DOM的呈现速度很快,因为它不必在屏幕上输出任何内容(无需进行重排或重新绘制)。
那么虚拟DOM如何解决问题? React无需重新渲染整个Real DOM,而是重新渲染整个Virtual DOM。然后,它在Real DOM和Virtual DOM之间进行区分以找出更新Real DOM所需的最小更改,并且仅在Real DOM中进行那些更改。这样,整个组件将被重新渲染,但仅在Real DOM中完成了所需的最小更改。
因此,当说“使用虚拟DOM React仅更新那些需要更新的元素”(我的问题中的第1点)时,这意味着React正在克服其自身方法的局限性(呈现整个UI的方法从头开始)。
此answer使用类推来解释相同的概念。
我已经看到一些答案,指出使用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。
分批处理的第二点确实是一个优势。
要了解原因,首先我们必须知道浏览器本身试图对DOM上的写操作进行批处理(请参阅此answer)。例如,如果我们对DOM进行2次写操作,则浏览器会将它们排队,并在javascript线程执行完成时一起执行两次写操作。这是有益的,因为不是每次写入都进行两次单独的重排,而是两次写入都在一次重排中完成。
但是这种批次并非总是可行。如果我们正在从DOM中读取可能受先前写入影响的内容,则必须立即执行这些写入(请参见此answer)。考虑以下示例(取自此answer)-
存在一个具有一定宽度的元素E。它有一个子元素C。C的宽度是E的宽度的100%。现在我们执行以下操作-
写入1-将E的宽度更改为一些新值
读取1-获得C的宽度
写入2-DOM中的一些随机更改
[在上述情况下,浏览器不会批量写入1和3。这是因为从读取2获得的值受写入1的影响。当我们在写入1中更改E的宽度时,它也会更改C的宽度。如果我们不写1而是读1,那么读1将为我们提供C的旧宽度。因此,在这种情况下,浏览器不会将写1排队并立即执行。
虚拟DOM启用批处理,因为它充当用户和真实DOM之间的一层。我们所做的任何读取或写入操作都是在虚拟DOM上完成的,而不是在真实DOM上完成的。在虚拟DOM上完成所有读取和写入之后,React将在真实DOM和虚拟DOM之间进行区分,然后writes对真实DOM进行最终更改。因此,最终我们只对Real DOM进行写操作,然后浏览器可以对它们进行批处理。
进一步阅读-
此post很好地总结了不同的javascript框架如何进行更改检测。
在此video中,Pete Hunt讨论了UI呈现的各种方法以及React的优点。他还讨论了React的关键设计决策-每次更新都重新渲染整个应用程序。