Patch for handwritten Vue2 series - diff

When learning becomes a habit, knowledge becomes common sense. Thank you for your attention , likes , favorites and comments .

New videos and articles will be sent on the WeChat public account as soon as possible, welcome to follow: Li Yongning lyn

The article has been included in the github repository liyongning/blog . Welcome to Watch and Star.

cover

foreword

In the previous article, the initial rendering of the handwritten Vue2 series completed the initial rendering of the original tags, custom components, and slots. Of course, it also involves the principles of v-bind, v-model, and v-on instructions. After the first render, it's time for the next update:

The responsive data is updated -> the setter intercepts the update operation -> dep notifies the watcher to execute the update method -> then execute the updateComponent method to update the component -> execute the render to generate a new vnode -> pass the vnode to the vm._update method -> call the patch Method -> execute patchVnode for DOM diff operation -> complete update

Target

Therefore, the goal of this article is to implement DOM diff and complete subsequent updates. There is only one knowledge point involved: DOM diff.

accomplish

Next, start to implement DOM diff to complete the subsequent update of responsive data.

patch

/src/compiler/patch.js

/**
 * 负责组件的首次渲染和后续更新
 * @param {VNode} oldVnode 老的 VNode
 * @param {VNode} vnode 新的 VNode
 */
export default function patch(oldVnode, vnode) {
  if (oldVnode && !vnode) {
    // 老节点存在,新节点不存在,则销毁组件
    return
  }

  if (!oldVnode) { // oldVnode 不存在,说明是子组件首次渲染
  } else {
    if (oldVnode.nodeType) { // 真实节点,则表示首次渲染根组件
    } else {
      // 后续的更新
      patchVnode(oldVnode, vnode)
    }
  }
}

patchVnode

/src/compiler/patch.js

/**
 * 对比新老节点,找出其中的不同,然后更新老节点
 * @param {*} oldVnode 老节点的 vnode
 * @param {*} vnode 新节点的 vnode
 */
function patchVnode(oldVnode, vnode) {
  // 如果新老节点相同,则直接结束
  if (oldVnode === vnode) return

  // 将老 vnode 上的真实节点同步到新的 vnode 上,否则,后续更新的时候会出现 vnode.elm 为空的现象
  vnode.elm = oldVnode.elm

  // 走到这里说明新老节点不一样,则获取它们的孩子节点,比较孩子节点
  const ch = vnode.children
  const oldCh = oldVnode.children

  if (!vnode.text) { // 新节点不存在文本节点
    if (ch && oldCh) { // 说明新老节点都有孩子
      // diff
      updateChildren(ch, oldCh)
    } else if (ch) { // 老节点没孩子,新节点有孩子
      // 增加孩子节点
    } else { // 新节点没孩子,老节点有孩子
      // 删除这些孩子节点
    }
  } else { // 新节点存在文本节点
    if (vnode.text.expression) { // 说明存在表达式
      // 获取表达式的新值
      const value = JSON.stringify(vnode.context[vnode.text.expression])
      // 旧值
      try {
        const oldValue = oldVnode.elm.textContent
        if (value !== oldValue) { // 新老值不一样,则更新
          oldVnode.elm.textContent = value
        }
      } catch {
        // 防止更新时遇到插槽,导致报错
        // 目前不处理插槽数据的响应式更新
      }
    }
  }
}

updateChildren

/src/compiler/patch.js

/**
 * diff,比对孩子节点,找出不同点,然后将不同点更新到老节点上
 * @param {*} ch 新 vnode 的所有孩子节点
 * @param {*} oldCh 老 vnode 的所有孩子节点
 */
function updateChildren(ch, oldCh) {
  // 四个游标
  // 新孩子节点的开始索引,叫 新开始
  let newStartIdx = 0
  // 新结束
  let newEndIdx = ch.length - 1
  // 老开始
  let oldStartIdx = 0
  // 老结束
  let oldEndIdx = oldCh.length - 1
  // 循环遍历新老节点,找出节点中不一样的地方,然后更新
  while (newStartIdx <= newEndIdx && oldStartIdx <= oldEndIdx) { // 根为 web 中的 DOM 操作特点,做了四种假设,降低时间复杂度
    // 新开始节点
    const newStartNode = ch[newStartIdx]
    // 新结束节点
    const newEndNode = ch[newEndIdx]
    // 老开始节点
    const oldStartNode = oldCh[oldStartIdx]
    // 老结束节点
    const oldEndNode = oldCh[oldEndIdx]
    if (sameVNode(newStartNode, oldStartNode)) { // 假设新开始和老开始是同一个节点
      // 对比这两个节点,找出不同然后更新
      patchVnode(oldStartNode, newStartNode)
      // 移动游标
      oldStartIdx++
      newStartIdx++
    } else if (sameVNode(newStartNode, oldEndNode)) { // 假设新开始和老结束是同一个节点
      patchVnode(oldEndNode, newStartNode)
      // 将老结束移动到新开始的位置
      oldEndNode.elm.parentNode.insertBefore(oldEndNode.elm, oldCh[newStartIdx].elm)
      // 移动游标
      newStartIdx++
      oldEndIdx--
    } else if (sameVNode(newEndNode, oldStartNode)) { // 假设新结束和老开始是同一个节点
      patchVnode(oldStartNode, newEndNode)
      // 将老开始移动到新结束的位置
      oldStartNode.elm.parentNode.insertBefore(oldStartNode.elm, oldCh[newEndIdx].elm.nextSibling)
      // 移动游标
      newEndIdx--
      oldStartIdx++
    } else if (sameVNode(newEndNode, oldEndNode)) { // 假设新结束和老结束是同一个节点
      patchVnode(oldEndNode, newEndNode)
      // 移动游标
      newEndIdx--
      oldEndIdx--
    } else {
      // 上面几种假设都没命中,则老老实的遍历,找到那个相同元素
    }
  }
  // 跳出循环,说明有一个节点首先遍历结束了
  if (newStartIdx < newEndIdx) { // 说明老节点先遍历结束,则将剩余的新节点添加到 DOM 中

  }
  if (oldStartIdx < oldEndIdx) { // 说明新节点先遍历结束,则将剩余的这些老节点从 DOM 中删掉

  }
}

sameVNode

/src/compiler/patch.js

/**
 * 判断两个节点是否相同
 * 这里的判读比较简单,只做了 key 和 标签的比较
 */
function sameVNode(n1, n2) {
  return n1.key == n2.key && n1.tag === n2.tag
}

result

Well, here, the diff process of the virtual DOM is completed. If you can see the following renderings, it means everything is normal.

Video address: https://gitee.com/liyongning/typora-image-bed/raw/master/202203151929235.image

Jun-18-2021 09-11-18.gif

As you can see, the page has completely done the initial rendering and subsequent updates of responsive data. The content about the Computed computed property is still not displayed correctly, which is normal. Because this function has not been implemented yet, the computed computed property will be implemented next, which is the next content of the handwritten Vue2 series of computed .

Link

Thank you for your attention , likes , favorites and comments , see you in the next issue.


When learning becomes a habit, knowledge becomes common sense. Thank you for your attention , likes , favorites and comments .

New videos and articles will be sent on the WeChat public account as soon as possible, welcome to follow: Li Yongning lyn

The article has been included in the github repository liyongning/blog . Welcome to Watch and Star.

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324217675&siteId=291194637