51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 4216|回复: 0
打印 上一主题 下一主题

[原创] React虚拟DOM浅析

[复制链接]
  • TA的每日心情
    擦汗
    半小时前
  • 签到天数: 1047 天

    连续签到: 5 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2021-5-19 10:53:07 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    一、DOM简介
      1.DOM构造和布局
      浏览器在解析HTML文档时,会将每个标签元素抽象成DOM(Document Object Model,文档对象模型)的节点,按照标签元素层次分明的结构,将HTML文档构建成一棵DOM树,如图 1所示。

    图 1 DOM树示例


      浏览器按从上到下,从左到右的顺序,读取DOM树的文档节点,顺序存放到文档流。如果读取的节点是另一节点的子节点,将其按顺序存放在父节点的内部,且嵌套层级没有数量限制。

      2.DOM操作
      DOM定义了所有HTML元素的对象和属性,以及访问方法。通过DOM提供的方法,所有HTML元素(DOM树节点)均可被修改、创建或删除。图 2展示了一些常用的DOM操作方法。

    图 2 常用的DOM操作方法


      二、虚拟DOM简介
      1.虚拟DOM概念
      虚拟DOM(Virtual DOM)是在真实DOM树的基础上建立了一个抽象层,通过一个JavaScript对象表示真实DOM树结构,包括DOM树节点的标签名、属性、事件监听及子元素等。因此,虚拟DOM本质上是一个JS对象,如图3.b为图3.a中DOM树所对应的虚拟DOM对象。

    图 3.a DOM树示例



    图 3.b 虚拟DOM示例


      2.React创建虚拟DOM
      React.createElement(tag, attrs, ...children)方法图 4所示,第一参数是标签名,第二参数是属性对象,后面的参数是0到多个子节点。该方法负责根据源代码提供的组件属性信息生成图 3.b所示的虚拟DOM,然后通过ReactDOM.render()方法将创建的虚拟DOM转化为图 3.a所示的真实DOM。

    图 4 react创建虚拟DOM对象示例


      3.JSX语法创建虚拟DOM
      React使用JSX语法编写虚拟DOM对象,经过babel编译后会解析生成图 4所示的目标代码,其中包括组件的type(标签名)、key(主键)、ref以及props等属性,其中props包含className、style和children等信息,然后通过ReactDOM.render()方法将虚拟DOM映射到真实DOM上。

    图 5 JSX语法创建虚拟DOM示例


      4.虚拟DOM的意义
      React引入轻量级的虚拟DOM而不是直接对真实DOM进行操作,目的是为了实现对DOM树节点数据和状态所做的任何改动,都会被自动且高效的同步到虚拟DOM,通过与变更前的DOM树进行比较,统计出所有的差异,统一更新一次真实DOM树。
      React虚拟DOM在组件状态发生变化时,构造新的虚拟DOM树并依赖算法计算出与上一个虚拟DOM树的差异,只针对变化的部分执行原生DOM操作。相较于原生DOM每次数据发生改变就去创建一个真实DOM,经过比较去修改真实DOM的操作,React虚拟DOM将多次操作合并为一次操作,提高了渲染效率。为了计算出虚拟DOM差异,React Diff算法被提出。

      三、React Diff算法
      React Diff算法可以计算出虚拟DOM中真正发生变化的部分,并只针对该部分进行实际DOM操作,而非重新渲染整个页面,从而保证了每次更新后页面的高效渲染。
    传统diff算法通过循环递归对DOM节点进行依次对比,效率低下,算法复杂度达到O(n3),其中n是节点的总数。为了提升性能,依据三个前提策略:
      (1)Web UI中DOM节点跨层级的移动操作特别少;
      (2)拥有相同类的两个组件将会生成相似的树形结构;
      (3)对于同一层级的一组子节点,可以通过唯一key进行区分。
      react从tree diff、component diff、element diff三个层面对diff算法进行优化,保证了整体界面构建的性能。

      1.Tree Diff
      基于前提策略1,DOM节点跨层级的移动操作特别少,React对DOM树进行分层比较,两棵树只会对同一层次的节点进行比较,即同一父节点下的所有子节点,如图 6所示只会对相同颜色框内的DOM节点进行比较。如果发现节点不存在,那么这个节点以及它的子节点就会被完全删除,不会进行下一步的比较。这样只需要一次遍历,就可以完成整个DOM树的比较。

    图 6 分层比较示例


      对于DOM节点跨层级的移动操作,只有创建和删除。如图 7所示,A节点及所有子节点跨层级移动到B节点下,由于tree diff只会比较同层级节点,当发现A节点消失了,则会执行删除A节点的操作;而在B中发现多了一个子节点A,则以A为根节点的整个树作为子节点被重新创建。该操作明显影响了React性能,因此React官方建议不要进行DOM节点跨层级的操作,保持稳定的DOM结构有助于性能的提升。如若要实现跨层级效果,可以通过CSS隐藏或显示节点,而不是真的移除或添加DOM节点。

    图 7 跨层级的移动操作示例


      2.Component Diff
      React基于组件构建应用,如果节点是组件,先看组件类型:
      (1)如果是同一类型组件,按照原策略继续比较虚拟DOM树;
      (2)如果不是同一类型组件,则替换该组件及其所有子节点。
      如图 8所示。当组件B改变为组件E时,一旦React判断出B和E是不同类型的组件,就不会继续比较二者的结构,而是直接删除组件B,重新创建组件E。

    图 8 组件比较示例


      3.Element Diff
      当节点处于同一层级时,react diff提供三种节点层级操作:
      (1)插入:新的组件类型不在原集合中,需要对新节点执行插入操作。
      (2)移动:新的组件在原集合中,且element可更新使用(如修改位置),需要进行移动操作。
      (3)删除:原集合中的组件在新集合中不存在,或者存在但element发生改变,无法更新使用,则需要进行删除操作。
      如图 9所示,显而易见只对DOM树进行删除了节点A的操作,但根据上述节点层级操作中原理,其过程为:原集合中包含两个span节点,新集合中包含一个span节点,此时新旧集合进行差异比较,发现原集合中A节点的属性发生改变,B节点被删除,因此在新的虚拟DOM树中执行操作为:删除A节点->删除B节点->创建新的A插入。

    图 9 节点层级比较示例


      React发现由于位置发生改变导致出现多余的删除和创建操作,提供了一种简单的优化策略,即允许对同一层级的子节点添加唯一key进行区分。如图 10所示,对新旧集合进行差异比较,通过key值发现仅原集合中A节点在新集合中不存在,则只需要进行删除A节点的操作即可。

    图 10 具有唯一key值的节点层级比较


      值得注意的是,如果使用index作为key值,当删除其中一个节点时,那么数组的后一项会向前移动,此时数据与key值无法建立唯一的关联关系,从而失去唯一key值的意义。
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-15 09:51 , Processed in 0.065685 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表