React虚拟DOM|青训营笔记

73 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的第18天

原理

由于频繁的操作 DOM 会产生性能问题,比如造成浏览器的回流和重绘。

通过 JS 对象模拟 DOM 中的结点,再通过特定的 render 方法将其渲染成真实的 DOM 结点。将多次 DOM 修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数。

虚拟 DOM 是一个 JS 对象,包含结点类型 tagName、属性 props、子节点 children 三个属性。

 <div id="app">
   <p class="text">test</p>
 </div>

将上面的HTML元素转化为虚拟DOM:

 {
   tagName: 'div',
   props: {
     id: 'app'
   },
   chidren: [
     {
       tagName: 'p',
       props: {
         className: 'text'
       },
       chidren: [
         'test'
       ]
     }
   ]
 }

如何实现上述转化呢?Virtual DOM 算法有以下步骤:

  1. JavaScript 对象结构表示 DOM 树的结构;

    1. 用这个树构建一个真正的 DOM 树,插到文档当中
  2. 当状态变更的时候,重新渲染一棵对象树。然后用新的对象树和旧的对象树进行比较,记录两棵树差异(Diff算法

    • tree diff:两棵树只会对同一层级的节点进行比较,对不同层级的节点只有创建和删除。

      因此只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。如果发现节点已经不存在了,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。

      image.png

      如果是不同层级,并不会出现移动操作,而是重新创建。

      image.png

      当根节点发现子节点中 A 消失了,就会直接销毁 A;当 D 发现多了一个子节点 A,则会创建新的 A(包括子节点)作为其子节点。React diff 的执行情况:create A -> create B -> create C -> delete A

    • component diff:如果组件的 class 一致,则模式为相似的树结构;否则认为是不同树结构,从而替换整个组件下的所有子节点

      image.png 当 component D 改变为 component G 时,即使这两个 component 结构相似,一旦 React 判断 D 和 G 是不同类型的组件,就不会比较二者的结构,而是直接删除 component D,重新创建 component G 以及其子节点。

    • element diff:对于同一层级的一组子节点,它们可以通过唯一 id 进行区分

      • 插入:新的 component 类型不在老集合里,即全新的节点,需要对新节点执行插入操作。
      • 移动:新的 component 类型在老集合里,且 element 是可更新的类型,执行移动操作,复用以前的 DOM 节点
      • 删除:新集合没有老的 component ,执行删除操作。
  3. 把步骤 3 所记录的差异应用到步骤 2 所构建的真正的 DOM 树上,视图就更新了

    image.png

优化

新老集合进行 diff 差异化对比,发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D

image.png

这些都是相同的节点,但由于位置发生变化,导致需要进行繁杂低效的删除、创建操作,其实只要对这些节点进行位置移动即可。

对同一层级的同组子节点,添加唯一 key 进行区分。 此时 React 给出的 diff 结果为,B、D 不做任何操作,A、C 进行移动操作,即可。

image.png

注:不要用数组的 index 作为唯一 key