Skip to content

updateChildren子序列对比 ​

本节目标:理解“updateChildren子序列对比”的核心思路,并能把它用于实际开发或面试表达。

/**
 * 实现完整的DOM-DIFF算法
 * @param {*} parentDOM 父DOM节点
 * @param {*} oldVChildren 老的虚拟DOM儿子的数组
 * @param {*} newVChildren 新的虚拟DOM儿子的数组
 */
function updateChildren(parentDOM, oldVChildren, newVChildren) {
  oldVChildren = Array.isArray(oldVChildren) ? oldVChildren : oldVChildren ? [oldVChildren] : [];
  newVChildren = Array.isArray(newVChildren) ? newVChildren : newVChildren ? [newVChildren] : [];
  let keyedOldMap = {};
  let lastPlacedIndex = 0;//上一个不需要移动的老DOM节点的索引
  oldVChildren.forEach((oldVChild, index) => {
    let oldKey = oldVChild.key || index;//如果提供了key,会使用key作为唯一标识,如果没有提供,会使用索引
    keyedOldMap[oldKey] = oldVChild;
  });
  //存着将要进行的操作
  let patch = [];
  //循环新数组
  newVChildren.forEach((newVChild, index) => {
    newVChild._mountIndex = index;//设置虚拟DOM的挂载索引为index
    let newKey = newVChild.key || index;
    let oldVChild = keyedOldMap[newKey];
    if (oldVChild) {
      //如果找到了,按理应该在此判断类型,省略....
      //先执行更新虚拟DOM元素 在React15里 DOM的更新和DOM-DIFF放在一起进行的。
      updateElement(oldVChild, newVChild);
      if (oldVChild._mountIndex < lastPlacedIndex) {
        patch.push({
          type: MOVE,
          oldVChild,
          newVChild,
          fromIndex: oldVChild._mountIndex,
          toIndex: index
        });
      }
      //如果此节点被复用了,把它从map中删除
      delete keyedOldMap[newKey];
      lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild._mountIndex);
    } else {//没有找到可复用老节点
      patch.push({
        type: PLACEMENT,
        newVChild,
        toIndex: index
      });
    }
  });
  /* Object.values(keyedOldMap).forEach(oldVChild=>{
      patch.push({
          type:DELETION,
          oldVChild,
          fromIndex:oldVChild._mountIndex
      });
  }); */
  //获取要移动 的元素 这里面只有B
  //此处我只是把B从界面中移动了,但是B还在是内存里的,B 这个DOM元素并没有被 销毁
  const moveChilds = patch.filter(action => action.type === MOVE).map(action => action.oldVChild);
  //现在keyedOldMap放着所有的剩下的元素
  Object.values(keyedOldMap).concat(moveChilds).forEach(oldVChild => {
    let currentDOM = findDOM(oldVChild);
    //获取到B D F三个真实DOM元素,然后从界面中删除
    currentDOM.parentNode.removeChild(currentDOM);
  });
  if (patch.length > 0) console.log(patch);
  patch.forEach(action => {
    let { type, oldVChild, newVChild, fromIndex, toIndex } = action;
    let childNodes = parentDOM.childNodes;//获取真实的子DOM元素的集合[A,C,E]
    if (type === PLACEMENT) {
      let newDOM = createDOM(newVChild);//根据虚拟DOM创建真实DOM
      let childDOMNode = childNodes[toIndex];//找一下目标索引现在对应的真实DOM元素
      if (childDOMNode) {//如果此位置 上已经 有DOM元素的,插入到它前面是
        parentDOM.insertBefore(newDOM, childDOMNode);
      } else {
        parentDOM.appendChild(newDOM);//添加到最后就可以了
      }
    } else if (type === MOVE) {
      let oldDOM = findDOM(oldVChild);//找到老的真实DOM 还可以把内存中的B取到,插入到指定的位置 B
      let childDOMNode = childNodes[toIndex];//找一下目标索引现在对应的真实DOM元素
      if (childDOMNode) {//如果此位置 上已经 有DOM元素的,插入到它前面是
        parentDOM.insertBefore(oldDOM, childDOMNode);
      } else {
        parentDOM.appendChild(oldDOM);//添加到最后就可以了
      }
    }
  });
  /*  let maxChildrenLength = Math.max(oldVChildren.length, newVChildren.length);
   //oldChildren=3 newChildren=2   oldChildren=2 newChildren=3
   for (let i = 0; i < maxChildrenLength; i++) {
       //试图取出当前的节点的下一个,最近的弟弟真实DOM节点
       let nextVdom = oldVChildren.find((item, index) => index > i && item && findDOM(item));
       compareTwoVdom(parentDOM, oldVChildren[i], newVChildren[i], findDOM(nextVdom));
   } */
}

本站总访问 — 次