vue2 的patch函数

114 阅读3分钟

vue 的patch函数

目录位置: src\core\vdom\patch.js

1.patch函数从何而来

通过 ==createPatchFunction== 返回一个patch函数,该方法需要传入属性比较、节点操作等方法

伪代码

function createPatchFunction (backend) {
  let i, j
  const cbs = {}

  const { modules, nodeOps } = backend  // 获取属性比较、节点操作
  
  // ....内部方法
  
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    // .... patch 过程
  }
}

2、对内部方法的部分先解析理解

1、创建空内容的tag虚拟dom节点
  function emptyNodeAt (elm) {
    return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
  }
2、移除节点 对dom某些节点进行移除
  function removeNode (el) {
    const parent = nodeOps.parentNode(el)
    // element may have already been removed due to v-html / v-text
    if (isDef(parent)) {
      nodeOps.removeChild(parent, el)
    }
  }
3、isUnknownElement 是否是未知置标签
// 全局有个config 配置忽略tag
config.ignoredElements
4、sameVnode 比较两个vnode,判断是否需要进行patchVnode

最基本需要满足 key值、tag值相同,或者是个异步组件

function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}
5、createKeyToOldIdx(children, beginIdx, endIdx) 数组创建下标映射
function createKeyToOldIdx (children, beginIdx, endIdx) {
  let i, key
  const map = {}
  for (i = beginIdx; i <= endIdx; ++i) {
    key = children[i].key
    if (isDef(key)) map[key] = i
  }
  return map
}

使用demo

const list = [{key: a}, {key:b}]

const map = createKeyToOldIdx(list, 0, 1)

// map = {a: 0, b: 1}

3、patch函数源码解析

function patch (oldVnode, vnode, hydrating, removeOnly) {

    //新节点vnode为空,代表销毁改节点,调用destroy钩子 区别于生命周期的钩子
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    
    // 该数组其实是收集组件节点的,用来维护一个插入队列。
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) { //oldVnode不存在 = null或者undefined
      // empty mount (likely as component), create new root element
      // 分支1、初始化走这里
      
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
      
    } else {
    
    
      const isRealElement = isDef(oldVnode.nodeType) // 是真实节点不为null或者undefined
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node // 都存在vnode
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      } else {
        // 这里之下都是处理初始化的
        if (isRealElement) {
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          // ssr的渲染处理
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            }
            
          // ------------------------ 这两步不知道干嘛的
          // either not server-rendered, or hydration failed.
          // create an empty node and replace it
          oldVnode = emptyNodeAt(oldVnode) // dom树转vnnode节点 细节先蔽先
        }

        // replacing existing element
        const oldElm = oldVnode.elm // 获得dom树
        const parentElm = nodeOps.parentNode(oldElm) // 获得dom节点

        // create new node 创建新vnode
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition + _leaveCb 目前估计是
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm, // 父节点
          nodeOps.nextSibling(oldElm) //追加的地方
        )

        // update parent placeholder node element, recursively
        if (isDef(vnode.parent)) {
          let ancestor = vnode.parent
          const patchable = isPatchable(vnode)
          while (ancestor) {
            for (let i = 0; i < cbs.destroy.length; ++i) {
              cbs.destroy[i](ancestor)
            }
            ancestor.elm = vnode.elm
            if (patchable) {
              for (let i = 0; i < cbs.create.length; ++i) {
                cbs.create[i](emptyNode, ancestor)
              }
              // #6513
              // invoke insert hooks that may have been merged by create hooks.
              // e.g. for directives that uses the "inserted" hook.
              const insert = ancestor.data.hook.insert
              if (insert.merged) {
                // start at index 1 to avoid re-invoking component mounted hook
                for (let i = 1; i < insert.fns.length; i++) {
                  insert.fns[i]()
                }
              }
            } else {
              registerRef(ancestor)
            }
            ancestor = ancestor.parent
          }
        }

        // destroy old node  删除旧节点
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }