React虚拟DOM原理和DOM diff

282 阅读3分钟

虚拟DOM

1.虚拟DOM是什么?

虚拟DOM就是虚拟节点。React用JS对象来模拟真实的DOM节点,然后将其渲染成真实的DOM节点。

2.怎么实现?

  • 第一步是模拟 用JSX语法写出来的div其实就是一个虚拟节点:
    <div id ="test">
           <span class="red">hello world</span>
    </div>

这代码会得到这样一个对象:

  tag: 'div',
  props: {
    id: 'test',
  },
  childred: [
    {
      tag: 'span',
      props: {
        className: 'red',
      },
      children: ['hello world'],
    },
  ],
};

能做到这一点是因为JSX语法会被转义为createElement函数调用(也叫h函数),如下:

React.createElement('div', { id: 'test' }, 
      React.createElement('span', { class: 'red' }, 'hello world'));
  • 第二步是将虚拟节点渲染成真实的节点
function render(vdom) {
  //如果是字符串或者数字,创建一个文本节点
  if (typeof vdom === 'string' || typeof vdom === 'number') {
    return document.createTextNode(vdom);
  }
  const { tag, props, childred } = vdom;
  //创建真实DOM
  const element = document.createElement(tag);
  //设置属性
  setProps(element, props);
  //遍历子节点,并获取创建真实DOM,插入到当前节点
  childred.map(render).forEach(element.appendChild.bind(element));

  //虚拟DOM中缓存真实DOM节点
  vdom.dom = element;

  //返回DOM节点
  return element;
}

function setProps() {} //略

注意,如果节点发生变化,并不会直接把新虚拟节点渲染到真实节点,而是先经过diff算法得到一个patch再更新到真实节点上。

3.解决了什么问题?

  • DOM 操作性能问题,通过虚拟DOM和diff算法减少不必要的DOM操作,保证性能不太差
  • DOM操作不方便问题。以前各种DOM API要记住,现在只有setState

DOM diff

1.DOM diff是什么?

DOM diff 是对比两颗虚拟DOM树的算法。当组件变化时,会render出一个新的虚拟虚拟DOM,diff算法对比新旧虚拟DOM树之后,得到一个patch,然后React用patch来更新真实DOM。

2.怎么做?

  • 首先对比两棵树的根节点
    • 如果根节点的类型改变了,比如div变成了p,那么认为整棵树都变了,不再对比子节点,直接删除对应的真实DOM树,创建新的真实DOM树。
    • 如果根节点的类型没变,就直接看属性的变了没有,没变保留对应的真实节点,变了的话,更新该节点的属性,不重新创建节点。
  • 然后同时遍历两颗树的子节点,每个节点对比过程同上
    • 当在子节点的末尾添加一个元素时,这两个树之间的转换效果很好:
<ul>
  <li>first</li>
  <li>second</li>
</ul>

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

React 会匹配两<li>first</li>棵树,匹配两<li>second</li>棵树,然后插入<li>third</li>树。

    • 在开头插入一个元素的性能会更差
<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

React 会改变每个孩子,而不是意识到它可以保持<li>Duke</li><li>Villanova</li>子树完整。这种低效率可能是一个问题。

为了解决这个问题,React 支持一个key属性。当孩子有键时,React 使用键将原始树中的孩子与后续树中的孩子进行匹配。例如,key在我们上面的低效示例中添加 a 可以使树转换高效:

<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

现在 React 知道带有 key 的元素'2014'是新元素,带有 key 和 的元素'2015''2016'刚刚移动的。

注: React DOM diff没用到双端交叉对比,源码里面有解释

image.png