Vue2.x 源码中 的 diff 以及 对 key 的使用,以及和 react 之间的区别

16 篇文章 1 订阅
5 篇文章 0 订阅

一直不想写 vue diff 的过程,因为 这一块网络上讲的人实在是太多了,这里写了也只不过是拾人牙慧罢了,但是不写吧,又觉得心痒痒的,毕竟前面写了一篇 react 源码中 对 key 的使用 的

首先是几个工具函数   有关于更新节点的内容,看一下我的 vue 中 patch、patchVnode 函数(更新节点)

// 这里用来判断 一个值是否是 未定义的
export function isDef(v: any): boolean % checks {
  return v !== undefined && v !== null
}
// 对比 是不是 新节点
// 这里和之前的区别是, 没有设置 key 的情况下,key 被认为是相等的
function findIdxInOld(node, oldCh, start, end) {
  for (let i = start; i < end; i++) {
    const c = oldCh[i]
    if (isDef(c) && sameVnode(node, c)) return i
  }
}
// 两个 vnode 是否相同
function sameVnode(a, b) {
  return (
    // 需要注意的是 null === null 为 true
    // 也就是说,如果 没有设置 key 的话,那么就默认为 两个节点的key 是一致的
    // 对比于 react 的diff ,这里的判断 多出了 tag、comment 和 input 的判断
    // 而 react 则着重于 $$type 的判断
    // 这里 react 明显的 对于 jsx 的分类 更倾向于 自己的判断,而 vue 则是 倾向于 用户的输入
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        // 是否是 同一个类型的 input 标签,这个很好理解
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}
// 是否是 相同类型的 input 节点,比如 radio,text,checkbox等
function sameInputType(a, b) {
  if (a.tag !== 'input') return true
  let i
  const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
  const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
  return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}

真正 diff 的过程

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly

    if (process.env.NODE_ENV !== 'production') {
      checkDuplicateKeys(newCh)
    }
    // 而这里,也是和 react diff 的最大的不同之处了,
    // react 对于新旧节点只做了顺序的 查找 相同节点,而 vue 还做了 前后节点的 对比
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx]
        // 新旧节点的 排头 对比,相等,则复用
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
        // 新旧节点的 排尾 对比,相等,则复用
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
        // 旧节点的 排头 和新节点 的 排尾,相等,则复用
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
        // 旧节点的排尾 和新节点的 排头,相等,则复用
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        // 这里是 为 老节点的 key 和 位置 做一个 对应关系
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        // 查找 在 老节点的 key 对应 map 中有没有 可以复用的节点
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        // 没有可以复用的,那就 创建一个
        if (isUndef(idxInOld)) { // New element
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        } else {
          vnodeToMove = oldCh[idxInOld]
          // 然后再对比 input 的type 和 tag 等属性,一样的话,吧老节点 对应的内容制空,复用节点
          if (sameVnode(vnodeToMove, newStartVnode)) {
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
            oldCh[idxInOld] = undefined
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } else {
            // 虽然 key 相等,但是节点类型不同,创建新节点
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
          }
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
    // 如果老节点 比新节点少,就新增 节点,否则 删除多余的老节点
    if (oldStartIdx > oldEndIdx) {
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      removeVnodes(oldCh, oldStartIdx, oldEndIdx)
    }
  }
  1. 获取老节点的排头和排尾,以及新节点的排头和排尾
  2.  遍历一直到老节点排头和排尾都为 存在的值结束
  3. 老节点的排头和新节点的排头是相同节点的时候 进入 patchVnode ,开始进一步比较这两个节点,同时 新老节点的排头和排尾往后移动一位
  4. 老节点的排尾和新节点的排尾是相同节点 的时候,进入 patchVnode,同时新老节点的排尾 向前移动一位
  5. 老节点的排头和新节点的排尾 是相同节点 的时候,进入 patchVnode 深度比较,然后对 节点在 dom 上的位置进行移动,接着老节点的排头往后移动,新节点的排尾往前移动、
  6.  当 老节点的排尾和新节点的排头是相同节点 的时候,进入 patchVnode 深入比较,然后对 对应的节点在 dom 上的位置进行移动,然后 老节点的排尾向前移动,新节点的排头向后移动
  7. 当 前面的 3、4、5、6 都不符合之后,将 老节点的 key 和对应的index 作为一个 map 对象保存起来
  8. 然后将新节点剩下的节点 对应的 key 寻找老节点对应相同的 key
  9. 如果存在相同的节点,将对应的节点 进入 patchVnode,然后将老节点 key 对应的value 职位 undefined,也就是删除,接着将老节点插入
  10. 如果最后遍历结束,新节点还有剩余,那创建新节点,如果老节点还有剩余,那删除老节点

所以 react 和 vue 的 diff 之间的异同?

  1. 当 key 为 null,也就是没有设置 key 的时候,两个都是当作 相同的节点来进行的

  2. react 和 vue 中对应 两个节点是否 可以复用 有着不同的判断

  3. react 中的 复用,指的是 key 相等,然后 type 相等,这里的 type 是在创建的时候,根据节点类型 设置的 

  4. 而 vue 的 复用,指的是 key 相等,节点的 tag 类型相等,input 的 type 相等

  5. 而 vue diff 中的 优化,指的就是 对 排头排尾 的比较了,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值