React Virtual DOM Diff

2018-12-07  本文已影响0人  冬青_2f75

假设我们的DOM长这个样子

<ul class="list">
  <li>item1</li>
  <li>item2</li>
</ul>

如果用JS Object表达上面的 dom,表达方式如下

{
  type: 'ul',
  props: { 'class': 'list' },
  children: [{
    type: 'li', props: {}, children: ['item1']
  }, {
    type: 'li', props: {}, children: ['item2']
  }]
}

通过以下的函数,我们将虚拟DOM转换成真实的DOM

function createElement(node) {
    if (typeof node === 'string') {
        return document.createTextNode(node)
    }
    const $el = document.createElement(node.type)
    node.children
      .map(createElement)
      .forEach($el.appendChild.bind($el))  
    return $el
}

现在我们考虑Diff,假设我们有两个virtual trees ----old and new, 我们考虑下面几种不一样的情况:

  1. new tree里面有新的元素


    屏幕快照 2018-12-07 下午6.02.16.png

    实现方式如下:

 function updateElement($parent, newNode, oldNode) {
    if (!oldNode) {
      $parent.appendChild(createElement(newNode))
    }
}
  1. new tree里面删除了元素


    屏幕快照 2018-12-07 下午6.05.46.png

    当要删除一个元素时,我们不知道真实的DOM节点,但此时我们可以知道这个节点的position,所以可以通过position拿到真是的DOM,然后删掉它
    实现方式如下:

function updateElement($parent, newNode, oldNode, index = 0) {
  if (!newNode) {
      $parent.removeChild(
          $parent.childNodes[index]
      )
  }
}
  1. node 本身变了


    屏幕快照 2018-12-07 下午6.14.02.png

    首先,我们来检测change

function change(node1, node2) {
    return typeof node1 !=== typeof node2 ||
      typeof node1 === 'string' && node1 !=== node2 ||
      node1.type !== node2.type
}
function updateElement($parent, newNode, oldNode, index) {
    if (change(newNode, oldNode)) {
        $parent.replaceChild(
            createElement(newNode),
            $parent.childNodes[index]
        )
    }
}
  1. node本身没变,但是其children有可能变了


    屏幕快照 2018-12-07 下午6.29.24.png

    需要递归的比较children

function updateElement($parent, newNode, oldNode, index) {
  if (newNode.type) {
      const newLength = newNode.children.length
      const oldLength = oldNode.children.length
      for(let i = 0; i < newLength || i < oldLength; i++) {
        updateElement(
            $parent.childNode[index],
            newNode.children[i],
            oldNode.children[i],
            i
        )
      }
  }
}

updateElement 最终的实现方式如下:

function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  } else if (changed(newNode, oldNode)) {
    $parent.replaceChild(
      createElement(newNode),
      $parent.childNodes[index]
    );
  } else if (newNode.type) {
    const newLength = newNode.children.length;
    const oldLength = oldNode.children.length;
    for (let i = 0; i < newLength || i < oldLength; i++) {
      updateElement(
        $parent.childNodes[index],
        newNode.children[i],
        oldNode.children[i],
        i
      );
    }
  }
}

参考文献:
https://medium.com/@deathmood/how-to-write-your-own-virtual-dom-ee74acc13060

上一篇 下一篇

猜你喜欢

热点阅读