Vue3.0 Diff算法之patchKeyedChildren方法流程图

779 阅读1分钟

Vue3 Diff算法之patchKeyedChildren方法.png

流程详解

待补充

简化版代码

const patchKeyedChildren = (
  c1,
  c2
) => {
  let i = 0
  let l1 = c1.length,
    l2 = c2.length
  let e1 = l1 - 1 // 旧的最后一个index
  let e2 = l2 - 1 // 新的最后一个index

  // 1 和 2 预处理相同的前后节点

  // 1. 从头开始
  while (i <= e1 && i <= e2) {
    const n1 = c1[i]
    const n2 = c2[i]
    if (isSameVNodeType(n1, n2)) {
      // 判断为相同节点
      console.log('从头开始,patch: ', n1)
    } else {
      break
    }
    i++
  }

  // 2. 从尾部开始
  while (i <= e1 && i <= e2) {
    const n1 = c1[e1]
    const n2 = c2[e2]
    if (isSameVNodeType(n1, n2)) {
      // 判断为相同节点
      console.log('从尾部开始,patch: ', n1)
    } else {
      break
    }
    e1--
    e2--
  }

  /**
   * 预处理之后:
   * (a, b, c, d), (g)      e1 === 3
   * (a, b, c, d), e, f, (g)   e2 === 4
   * i === 4
   */

  /**
   * 3. 新增节点
   * 如果i > e1 && i <= e2,说明新arr有新增的节点
   */
  if (i > e1) {
    if (i <= e2) {
      while (i <= e2) {
        console.log('新增的节点patch: ' + c2[i])
        i++
      }
    }
  }

  // 4. 减少节点 --- 直接删除旧的节点
  else if (i > e2) {
    while (i <= e1) {
      console.log('删除旧节点:', c1[i])
      i++
    }
  }

  // 5. 有节点移动、新增或删除
  else {
    const s1 = i // 旧的初始index
    const s2 = i // 新的初始index

    // 5.1 建立新序列的Map
    const keyToNewIndexMap = new Map()
    for (let idx = s2; idx <= e2; idx++) {
      const nextChild = c2[idx]
      if (nextChild.key !== null) {
        keyToNewIndexMap.set(nextChild.key, idx)
      }
    }
    console.log(keyToNewIndexMap)

    // 5.2 循环旧序列
    let j
    const toBePatched = e2 - s2 + 1 // 总的需要patch的数量
    let patched = 0 // 已patch的数量
    let moved = false // 移动标记
    let maxNewIndexSoFar = 0 // 已遍历的待处理的 c1 节点在 c2 中对应的索引最大值
    const newIndexToOldIndexMap = new Array(toBePatched) // 代表新节点在旧序列中的位置,用于后面求最长递增子序列
    for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0 // 默认全部为新增节点,用0表示

    // 遍历 c1 中待处理的节点,判断否 c2 中是有相同 key 的节点存在。
    for (i = s1; i <= e1; i++) {
      const prevChild = c1[i]
      if (patched >= toBePatched) {
        // 已patch的数量比未patch的多,说明多余,删掉节点
        console.log('已patch的数量比未patch的多,说明多余,删掉节点')
        continue
      }
      // 否则,调用 patch 函数,并记录节点在 c1 中的索引。
      // 同时,记录节点在 c2 中的最大索引
      let newIndex
      newIndex = keyToNewIndexMap.get(prevChild.key) // 取旧节点在新序列里的索引
      console.log(`旧节点${prevChild.key}, 在新序列里面对应索引的:${ newIndex !== undefined ? newIndex : '找不到' }`)
      if (newIndex === undefined) {
        // 在旧的里面找不到了,说明没用,删掉
        console.log(prevChild, '在新序列中没有了,删除节点')
      } else {
        // 把0当成特殊值(代表新增的节点),所以其他位置用i + 1代替
        newIndexToOldIndexMap[newIndex - s2] = i + 1
        if (newIndex >= maxNewIndexSoFar) {
          maxNewIndexSoFar = newIndex
        } else {
          // 假如节点在 c2 中的索引位置小于这个最大索引,那么说明是有元素需要进行移动。
          moved = true
        }
        console.log('patch - 复用节点:', prevChild)
        patched++
      }
    }

    console.log('新节点在旧序列中的位置:', newIndexToOldIndexMap)
    // 5.3
    for (i = toBePatched - 1; i >= 0; i--) {
      if (newIndexToOldIndexMap[i] === 0) {
        console.log('新创建:', c2[i + s2])
      } else if (moved) {
        console.log('移动旧节点:', c2[i + s2])
      }
    }
  }
}