React 与 虚拟DOM

虚拟 DOM 这个概念,相信在接触过 React 或者 Vue 的同学一定都不陌生,但是在实际开发中,跟一些同学讨论问题时却发现同学们对 虚拟 DOM 的作用和 React 的渲染过程 的理解有些偏差。这里也给自己做个备忘吧。

要解释 虚拟DOM ,我们还要从 JSX 讲起。

JSX 是什么?

要讲 React ,肯定是绕不过 JSX ,JSX 是 React 灵魂所在。那 JSX 是什么?

是组件?那组件又是什么?

其实 官网 已经有了明确的说明,JSX 不过是一层语法糖,比如下面的 JSX 代码:

<MyButton color="blue" shadowSize={2}>
  Click Me
</MyButton>

编译之后,就会变成:

React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Click Me'
)

如果再复杂一点:

<div className="cn">
    <Header> Hello, This is React </Header>
    <div>Start to learn right now!</div>
    Right Reserve.
</div>

编译之后:

React.createElement(
        'div',
        { className: 'cn' },
        React.createElement(
            Header,
            null,
            'Hello, This is React'
        ),
        React.createElement(
            'div',
            null,
            'Start to learn right now!'
        ),
        'Right Reserve'
    )

如果你想测试一些特定的 JSX 会转换成什么样的 JavaScript,你可以尝试使用 在线的 Babel 编译器

在代码中也找到了对应的位置,官网上也有说到这一点。

我们可以看到 createElement 这个方法的入参只有三个,也就是刚才看到的编译出来的三个入参。最终会返回一个叫 ReactElement 的对象。

function createElement(type, config, children) {
  var propName; // Reserved names are extracted

  var props = {};
  var key = null;
  var ref = null;
  var self = null;
  var source = null;

  ...

  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}

我们再来看一下 ReactElement 是什么。

var ReactElement = function (type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,
    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,
    // Record the component responsible for creating this element.
    _owner: owner
  };

  ...
  return element;
};

诶,最后返回的是一个对象, JSX 最后会变成一个对象?

是的!但是 Render 函数中,返回的不止有 ReactElement 还有一些其他的东西,主要有这下面几类:

  • React 元素。通常通过 JSX 创建。例如,<div /> 会被 React 渲染为 DOM 节点,<MyComponent /> 会被 React 渲染为自定义组件,无论是 <div /> 还是 <MyComponent /> 均为 React 元素。
  • 数组或 fragments。 使得 render 方法可以返回多个元素,其实也就是我们平时使用的 <></>。欲了解更多详细信息,请参阅 fragments 文档。
  • Portals。可以渲染子节点到不同的 DOM 子树中。欲了解更多详细信息,请参阅有关 portals 的文档
  • 字符串或数值类型。它们在 DOM 中会被渲染为文本节点。
  • 布尔类型或 null。什么都不渲染。(主要用于支持返回 test && <Child /> 的模式,其中 test 为布尔类型。)

官网中也有说明:

虚拟 DOM 树

说完了 ReactElement ,我们就可以来说一下 虚拟 DOM 了。虚拟 DOM 树其实是 DOM 树的一个映射,通过 虚拟 DOM 树,React 可以还原出一颗完整 DOM 树,而 DOM 树也可以使用 虚拟 DOM 树来保存其结构。有点像 序列化 和 反序列化 的关系。

虚拟 DOM 节点 主要有这几类:

  • ReactDOMTextComponent:用来负责text node对应的虚拟 DOM
  • ReactDOMComponent:用来负责html标签对应的虚拟 DOM
  • ReactEmptyComponent : 用来负责 null ,false 的虚拟 DOM
  • ReactCompositeComponent:用来负责继承 React.Component 对应的虚拟 DOM,也就是自定义的 组件。

这几种类型涵盖了 Render 函数中返回的类型,

React 元素、数组或者Fragment、Portals,最后会解析为 ReactDOMComponent ,

字符串和数组类型,最后会解析为 ReactDOMTextComponent ,

布尔类型或 null 最后会解析为 ReactEmptyComponent。

也就是说我们所写的、所使用的 JSX 或者 组件 最后都会变成一个个的 虚拟 DOM,组合起来,最后会变成一个 虚拟 DOM 树。也就是我们常说的 虚拟 DOM 树。

那虚拟 DOM 节点又是一个什么东西?

其实也是一个 JS 对象 ,里面有 一堆的属性,和一些用来进行挂载、更新等渲染相关的方法。我们来看一下 15.0.0 版本的代码:

为什么要使用虚拟 DOM?

为什么要使用虚拟 DOM 这个问题,还要从 React 的开发模式说起。

文章看到这里,我想在座的各位应该都知道 React 是非常典型的 MVVM 设计,需要更新数据的时候,开发者只需要通过 setState 直接把数据塞给 React 就可以了,完全不用考虑数据的渲染。在这之前,传统的 Web前端开发,可能还在使用 JQ 或者手动去更新界面数据,过程比较繁琐还容易出错,操作不当还会有性能的问题,如果使用的是 MVVM 的这种开发框架,直接把数据一给,就完事了。就像如果你自己做饭,需要买菜、洗菜、切菜、做饭、洗碗,如果使用了 MVVM 的框架,就像是点外卖一样,只需要在下单,等外卖到就可以吃饭了,吃完还不用洗碗,你说爽不爽。

但是岁月静好的背后一定有人在为你负重前行,这个人就是 React 的渲染机制,其中最关键之一就是 虚拟DOM,React 的所有工作几乎都基于 虚拟DOM 完成的。

所以虚拟 DOM 有什么用?

最主要的作用就是提升性能。

其实就像 Android 的视图渲染一样,操作 DOM 也是一个比较耗性能的操作,如果频繁触发,就会导致性能问题,界面出现卡顿。虚拟 DOM 的存在,就是尽量减少对 DOM 的操作,从而提高性能。

这一步是怎么实现的?

再回到刚才讲的 虚拟 DOM 节点上,当 React 触发更新的时候,会依次调用 Render 函数,返回一个个的 虚拟DOM 节点,最后组装一颗新的虚拟 DOM 树,再跟旧的 虚拟 DOM 树 进行比较,得出需要更新的部分,然后再把这部分更新到 DOM 中。

其中跟性能相关有两个关键点,一个是批处理、另一个是Diff算法。

批处理:

在 React 中,并不是每一次 setState 都会触发一次更新,可能是多个 setState 最后才会合成并触发一次更新,这个对于性能的优化是非常关键的。官网中也有说明:

Diff算法:

当更新的逻辑被触发之后,会生成一颗新的 DOM 树,新旧两颗 DOM 树进行比较,得出差异。我们知道完全对比两颗树的性能是很差的,即便是最优的算法,时间复杂度也要达到 O(n^3),太慢了。React 中使用的 Diff 算法,并没有完全比较两颗树之间的差异,如果原来节点位置的 类型 变化时,会直接替换这颗子树,还有就是通过 key 来告诉 React 的元素位置变化,更多详细的规则可以查看官网的描述。

这套 Diff 算法,使时间复杂度从 O(n^3) 降低到了 O(n) ,整整降低了一个量级。

一些误解

Render 被调用,DOM 就更新了?

有些同学在看到 Render 函数被调用了,就以为这个组件被更新了,重新渲染了。其实不然,这个过程仅仅还只是在生成新的虚拟 DOM 树而已,还没到渲染 DOM 的步骤。

虚拟 DOM 牛皮,速度最快?

有些同学看到虚拟DOM 这套机制,就觉得很牛逼,甚至觉得比原生操作更快。借鉴了知乎上的回答:没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。针对任何一个 benchmark,我都可以写出比任何框架更快的手动优化,但是那有什么意义呢?在构建一个实际应用的时候,你难道为每一个地方都去做手动优化吗?出于可维护性的考虑,这显然不可能。

最后,逛了一圈知乎,似乎都在说现在的Web前端的趋势是 独立状态帧 + 多线程渲染 、函数式编程。其实 React 16 中引入的 函数式组件 + Hook,不就是这个趋势的实现吗?要学的东西又多了一些。。。。。。

本文由博客一文多发平台 OpenWrite 发布!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容