Skip to content

双端对比 patch

从左开始对比

两个 child 不相等(从左往右比对)

两个 child 不相等(从右往左比对)

point (代表 c1, c2 开始相似节点结束的位置)

e1 代表 c1 从尾部遍历的结束位置

e2 代表 c2 从尾部遍历的结束位置

point > e1 && point <= e2

也就是说新增了 vnode

point > e2 && point <= e1

这种情况的话说明新节点的数量是小于旧节点的数量的 那么我们就需要把多余的 remove

左右两边都比对完了,然后剩下的就是中间部位顺序变动的

我梦当然可以讲这部分 c1 全部 remove c2 全部 patch 来进行实现

这个时候需要使用最长子序列来减少 dom 操作

判断是否发生移动 做插入, 删除 操作

javascript
  function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) {
    const len1 = c1.length
    const len2 = c2.length
    let point = 0

    function isSameVnode(n1, n2) {
      return n1.type === n2.type && n1.key === n2.key
    }
    while (point < len1 && point < len2) {
      const n1 = c1[point]
      const n2 = c2[point]

      if (isSameVnode(n1, n2)) {
        patch(n1, n2, container, parentComponent, parentAnchor)
        point++
      }
      else {
        break
      }
    }

    let e1 = len1 - 1; let e2 = len2 - 1
    while (point <= e1 && point <= e2) {
      const n1 = c1[e1]
      const n2 = c2[e2]

      if (isSameVnode(n1, n2))
        patch(n1, n2, container, parentComponent, parentAnchor)
      else
        break
      e1--
      e2--
    }
    if (point > e1 && point <= e2) {
      const nextPos = e2 + 1
      const anchor = nextPos < len2 ? c2[nextPos].el : null

      while (point <= e2) {
        patch(null, c2[point], container, parentComponent, anchor)
        point++
      }
    }
    else if (point <= e1 && point > e2) {
      while (point <= e1) {
        hostRemove(c1[point].el)
        point++
      }
    }
    else {
      // 中间对比
      const toBePatch = e2 - point + 1
      let patched = 0
      const keyToNewIndexMap = new Map()
      const newIndexToOldIndexMap = Array.from({ length: toBePatch }).fill(0) as number[]
      let move = false
      let maxIndex = 0
      for (let i = point; i < len2; i++)
        keyToNewIndexMap.set(c2[i].key, i)

      for (let i = point; i <= e1; i++) {
        const prevChild = c1[i]
        const newIndex = keyToNewIndexMap.get(prevChild.key)
        if (patched >= toBePatch) {
          hostRemove(prevChild.el)
          continue
        }

        if (newIndex !== undefined) {
          if (newIndex >= maxIndex)
            maxIndex = newIndex
          else
            move = true
          patch(prevChild, c2[newIndex], container, parentComponent, null)
          newIndexToOldIndexMap[newIndex - point] = i + 1
          patched++
        }
        else { hostRemove(prevChild.el) }
      }

      const increasingNewIndexSequence = move ? getSequence(newIndexToOldIndexMap) : []
      let j = increasingNewIndexSequence.length - 1
      for (let i = toBePatch - 1; i >= 0; i--) {
        const nextIndex = point + i
        const nextChild = c2[nextIndex]
        const anchor = nextIndex + 1 < len2 ? c2[nextIndex + 1].el : null
        if (newIndexToOldIndexMap[i] === 0) { patch(null, nextChild, container, parentComponent, anchor) }
        else if (move) {
          if (j < 0 || i !== increasingNewIndexSequence[j])
            hostInsert(nextChild.el, container, anchor)
          else
            j--
        }
      }
    }
  }

最递增长子序列

javascript


function getSequence(arr: number[]): number[] {
  const p = arr.slice()
  const result = [0]
  let i, j, u, v, c
  const len = arr.length
  for (i = 0; i < len; i++) {
    const arrI = arr[i]
    if (arrI !== 0) {
      j = result[result.length - 1]
      if (arr[j] < arrI) {
        p[i] = j
        result.push(i)
        continue
      }
      u = 0
      v = result.length - 1
      while (u < v) {
        c = (u + v) >> 1
        if (arr[result[c]] < arrI)
          u = c + 1
        else
          v = c
      }
      if (arrI < arr[result[u]]) {
        if (u > 0)
          p[i] = result[u - 1]

        result[u] = i
      }
    }
  }
  u = result.length
  v = result[u - 1]
  while (u-- > 0) {
    result[u] = v
    v = p[v]
  }
  return result
}