浅谈React的Vistual DOM

1,279 阅读4分钟

什么是Vistual DOM

在许多UI框架中,我们可以通过render函数来构建应用程序,例如下面的React组件:

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

也可以不使用JSX:

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

这两者的结果是一致的:一个对象。这个对象就是Vistual DOM,当组件每次更新都会创建一个新对象,React会将新老对象进行比较,整个过程叫做reconcile,差异比较(diff)后的结果会commit更新,也就是应用到页面的真实DOM元素。

因此,Vistual DOM本质就是用JavaScript对象描述真实DOM,与真实DOM构成映射关系

为什么React使用Vistual DOM

前面说到Vistual DOM是真实DOM的一层映射,为什么React需要这层映射关系,难道不可以直接操作真实DOM?这要从React的更新机制说起,React的更新机制是「一次更新,整体更新」。每次组件状态变化都是整个React应用程序状态的对齐。那就面临着一个问题:实际页面的更新往往是小范围元素的变化,却要引起整个页面的重绘,存在大量性能消耗。所以React使用Vistual DOM作为缓冲层,通过JS计算过滤出必要的更新操作,最后交给浏览器更新页面。

Vistual DOM真的快吗

很多人可能认为React使用Vistual DOM, 是因为它比真实DOM快,但这却是对Vistual DOM最大的误解,我们可以追溯到2013年前 React 核心团队成员 Pete Hunt在Rethinking Best Practices 中的演讲。

This is actually extremely fast, primarily because most DOM operations tend to be slow. There's been a lot of performance work on the DOM, but most DOM operations tend to drop frames.

Google翻译:这实际上非常快,主要是因为大多数 DOM 操作往往很慢。在 DOM 上有很多性能工作,但大多数 DOM 操作都倾向于丢帧。

Vistual DOM diff是React附加的操作,最终还是要调用原生DOM API更新页面,相较真实DOM怎么就快了?

重点在于Pete Hunt后面的解释:

React is not magic. Just like you can drop into assembler with C and beat the C compiler, you can drop into raw DOM operations and DOM API calls and beat React if you wanted to. However, using C or Java or JavaScript is an order of magnitude performance improvement because you don't have to worry about the specifics of the platform. With React you can build applications without even thinking about performance and the default state is fast.

Google翻译:React不是魔术。就像您可以使用 C 进入汇编程序并击败 C 编译器一样,如果您愿意,您可以进入原始 DOM 操作和 DOM API 调用并击败 React。但是,使用 C 或 Java 或 JavaScript 是一个数量级的性能改进,因为您不必担心平台的细节。使用 React,您甚至可以在不考虑性能的情况下构建应用程序,并且默认状态很快。

Vistual DOM与真实DOM性能的比较,需要结合更多场景辩证的看待,真实DOM的创建插入是昂贵的,同时Vistual DOM diff本身不是免费的,但JS层面的diff相对真实DOM节点的创建和更新,还是便宜太多。React只是使用Vistual DOM diff计算的成本去替换不必要DOM更新的成本,这两者的成本大小关系不是绝对的,而是动态的。

我们有必要去澄清:Vistual DOM不是绝对的快,而是足够快。即使开发者不做手动优化,React也可以给你过得去的性能。因此,Vistual DOM性能的天花板是在于diff算法优化(batching批处理等)加上开发者手动优化(shouldComponentUpdate、memo等)。

Vistual DOM的价值

我们需要从整个React层面看Vistual DOM带来的价值,主要有以下两点:

  • 抽象渲染过程,可以封装大量底层渲染操作,增加跨平台能力

    真实DOM涉及到的底层API非常复杂,例如创建一个div元素:document.createElement('div'),创建一个svg元素:document.createElementNS('http://www.w3.org/2000/svg', 'svg'),光元素创建方面,不同元素就要区分不同的创建方法。有了Vistual DOM,框架对渲染过程有了更多的控制权,简单的过程为: Vistual DOM -> render(独立的渲染模块) -> 目标平台元素节点。框架使用Vistual DOM的好处就是可以将这些底层的渲染操作统统简化,将渲染过程封装成独立的模块,例如ReactDom、ReactNative。同时由于Vistual DOM是纯JS,相同的React组件可以运行在多个平台,例如Node端,这样我们还可以实现同构应用(SSR)等。

  • 函数式编程

    Vistual DOM打开了函数式(声明式)的大门,即UI = f(props),代码可读性更强。

    // 命令式
    const div = document.createElement('div')
    div.className = 'greeting'
    div.textContent = 'Hello World'// 声明式
    function HelloMessage(props) {
      return (
        <div className="greeting">
          Hello World
        </div>
      );
    }
    

总结

基于以上几点,我们看到React选择了「一次更新,整体更新」的道路,引入Vistual DOM来实现足够快的性能,同时带来抽象渲染过程、跨平台、函数式编程的好处。 React已经将我们的开发方向从「面向真实DOM」构建页面转变为「面向Vistual DOM」构建页面,Vistual DOM已满足我们绝大多数的开发场景。