虚拟DOM方案真的性能好吗?(翻译)

本文翻译自 www.svelte.cn/blog/virtua…

“虚拟 DOM 很快”这样的神话或许不一定是对的

如果你近些年正在使用主流的前端框架,你可能会听说操作虚拟DOM比操作真实DOM更快这样的言论。人们也很好奇Svelte是如何做到在不使用虚拟DOM技术的情况下做到性能极佳的。

本篇文章我们就来一起聊聊。

什么是虚拟DOM

在许多前端框架中,你可能会通过render函数来渲染你的应用程序,就像下面这样:

function HelloMessage(props) {
  return (
    <div className="greeting">
      Hello {props.name}
    </div>
  );
}

或者不使用jsx也可以做到:

function HelloMessage(props) {
  return React.createElement(
    'div',
    { className: 'greeting' },
    'Hello ',
    props.name
  );
}

总的来说结果是一样的,你都会得到一个用于描述UI页面的javascript对象,这个对象就是虚拟DOM。任何时候你的组件状态发生改变(例如当上面例子中属性name发生该改变的时候),就会重新创建一个新的虚拟DOM对象。框架的工作就是对比新旧两个虚拟DOM,然后找到哪里发生了变化,然后把变化的部分映射成为真实的DOM。

这样的策略明智吗?

这种关于虚拟DOM的性能神话的误解的来源需要追溯到React在Rethinking Best Practices的发布会上,那个一个在2013年由React团队成员Pete Hunt的发布的一个研讨上提出的,他说:

这实际上非常快,主要是因为大多数 DOM 操作往往很慢。在 DOM 上做了很多性能工作,但大多数 DOM 操作都容易丢帧。

虚拟DOM方案真的性能好吗?(翻译)

但请稍等!虚拟 DOM 操作是对真实 DOM 的最终操作的补充。它可能更快的唯一方法是,如果我们将其与效率较低的框架进行比较(2013 年有很多可以解决的问题!),或者反对稻草人 – 另一种选择是做一些没人真正做过的事情:

onEveryStateChange(() => {
  document.body.innerHTML = renderMyApp();
});

Pete然后说明道:

React 不是魔法。就像您可以使用 C 进行汇编并击败 C 编译器一样,如果您愿意,您也可以投入原始 DOM 操作和 DOM API 调用并击败 React。然而,使用 C 或 Java 或 JavaScript 可以实现一个数量级的性能改进,因为您不必担心……平台的具体情况。使用 React,您可以构建应用程序,甚至无需考虑性能,并且默认状态很快。

但这不是重点。

所以是虚拟DOM性能不好吗?

不完全是。它更像是“虚拟 DOM 通常足够快”,但有一些注意事项。

React 最初的承诺是,您可以在每次状态更改时重新渲染整个应用程序,而无需担心性能。在实践中,我认为这并不准确。如果是的话,就不需要像 shouldComponentUpdate 这样的优化(这是一种告诉 React 何时可以安全地跳过组件的方法)。

即使使用 shouldComponentUpdate,一次性更新整个应用程序的虚拟 DOM 也是一项艰巨的工作。不久前,React 团队推出了一种名为 React Fiber 的东西,它允许将更新分解为更小的块。这意味着(除其他外)更新不会长时间阻塞主线程,尽管它不会减少总工作量或更新所需的时间。

那么开销来自哪里呢?

最明显的是……如果不首先将新的虚拟DOM与以前的快照进行比较,就不能对真实DOM应用更改。以前面的HelloMessage为例,假设name从’world’更改为’everybody’。

function HelloMessage(props) {
  return (
    <div className="greeting">
      Hello {props.name}
    </div>
  );
}
  1. 两个快照都包含一个父元素。在这两种情况下都是<div>,这意味着我们可以不必更新这个DOM节点

  2. 我们再检查这个元素上的的所有属性,以查看是否需要更改、添加或删除任何属性。在这两种情况下,我们都有一个单一的属性——一个className,其值为“greeting”。

  3. 再看子元素,我们看到文本已经改变,所以我们需要更新真正的DOM

在这三个步骤中,只有第三个步骤在这种情况下有价值,因为——就像绝大多数更新的情况一样——应用程序的基本结构没有改变。如果我们直接跳到步骤3,效率会高得多:

if (changed.name) {
  text.data = name;
}

(这几乎正是 Svelte 生成的更新代码。与传统的 UI 框架不同,Svelte 是一个编译器,它在构建时知道应用程序中的内容会如何变化,而不是等待在运行时完成工作。)

不仅仅是diff的原因

React 和其他虚拟 DOM 框架使用的比较算法速度很快。可以说,更大的开销在于组件本身。你就不能这样写代码了…

function StrawManComponent(props) {
  const value = expensivelyCalculateValue(props.foo);

  return (
    <p>the value is {value}</p>
  );
}

..因为无论 props.foo 是否更改,您都会在每次更新时不小心重新计算值。但以看似好得多的方式进行不必要的计算和分配是非常常见的:

function MoreRealisticComponent(props) {
  const [selected, setSelected] = useState(null);

  return (
    <div>
      <p>Selected {selected ? selected.name : 'nothing'}</p>

      <ul>
        {props.items.map(item =>
          <li>
            <button onClick={() => setSelected(item)}>
              {item.name}
            </button>
          </li>
        )}
      </ul>
    </div>
  );
}

在这里,我们在每次状态更改时生成一个新的虚拟 <li> 元素数组 – 每个元素都有自己的内联事件处理程序,无论 props.items 是否已更改。除非你对性能过分痴迷,否则你不会对其进行优化。毫无意义。速度够快了。但你知道什么会更快吗?不这样做。

React Hooks 加倍努力默认执行不必要的工作,并获得可预测的结果。
默认执行不必要的工作(即使该工作微不足道)的危险在于,您的应用程序最终将屈服于“千刀万剐”,一旦需要优化,就没有明确的瓶颈可以瞄准。

Svelte 的设计目的就是为了防止您陷入这种情况。

为什么其他框架会使用虚拟DOM呢

重要的是要理解虚拟DOM不是一个特性。它是达到目的的一种手段,目的是声明式的、状态驱动的UI开发。虚拟DOM很有价值,因为它允许您构建应用程序而无需考虑状态转换,性能通常足够好。这意味着更少的错误代码,更多的时间花在创造性的任务上,而不是乏味的任务上。

但事实证明,我们可以在不使用虚拟DOM的情况下实现类似的编程模型——这就是Svelte的用武之地。

原文链接:https://juejin.cn/post/7349327791554920460 作者:Story

(0)
上一篇 2024年3月24日 上午10:49
下一篇 2024年3月24日 上午10:59

相关推荐

发表回复

登录后才能评论