vue源码分析(十九)核心函数之patchVnode

我们先打开文件src/core/vdom/patch.js。代码如下:

function patchVnode (
    oldVnode,
    vnode,
    insertedVnodeQueue,
    ownerArray,
    index,
    removeOnly
  ) {
    if (oldVnode === vnode) {
      return
    }

    if (isDef(vnode.elm) && isDef(ownerArray)) {
      // clone reused vnode
      vnode = ownerArray[index] = cloneVNode(vnode)
    }

    const elm = vnode.elm = oldVnode.elm
    // 是否存在异步占位符
    if (isTrue(oldVnode.isAsyncPlaceholder)) {
     // 是否存在异步工厂函数,主要是异步组件会使用到,具体的可以看vue的异步组件 ‘https://cn.vuejs.org/v2/guide/components-dynamic-async.html‘
      if (isDef(vnode.asyncFactory.resolved)) {
        hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
      } else {
        vnode.isAsyncPlaceholder = true
      }
      return
    }

    // reuse element for static trees.
    // note we only do this if the vnode is cloned -
    // if the new node is not cloned it means the render functions have been
    // reset by the hot-reload-api and we need to do a proper re-render.
    if (isTrue(vnode.isStatic) &&
      isTrue(oldVnode.isStatic) &&
      vnode.key === oldVnode.key &&
      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
      vnode.componentInstance = oldVnode.componentInstance
      return
    }

    let i
    const data = vnode.data
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
      i(oldVnode, vnode)
    }

    const oldCh = oldVnode.children
    const ch = vnode.children
    if (isDef(data) && isPatchable(vnode)) {
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
    }
    if (isUndef(vnode.text)) {
      if (isDef(oldCh) && isDef(ch)) {
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
      } else if (isDef(ch)) {
        if (process.env.NODE_ENV !== 'production') {
          checkDuplicateKeys(ch)
        }
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        removeVnodes(oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {
        nodeOps.setTextContent(elm, '')
      }
    } else if (oldVnode.text !== vnode.text) {
      nodeOps.setTextContent(elm, vnode.text)
    }
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }

这个函数会对oldVnodevnode进行对比,然后进行DOM更新,下面我们就来仔细看看代码。

if (oldVnode === vnode) {
  return
}

首先就是对比新旧两个node节点是否相等,如果相等的话,就return,直接返回了。

if (isDef(vnode.elm) && isDef(ownerArray)) {
  // clone reused vnode
  vnode = ownerArray[index] = cloneVNode(vnode)
}

如果虚拟节点的elm属性存在的话,就说明有被渲染过了,如果ownerArray存在,说明存在子节点,如果这两点到成立,那就克隆一个vnode节点。

  if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key &&(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
      vnode.componentInstance = oldVnode.componentInstance
      return
 }

1、vnode是静态节点
2、oldVnode是静态节点
3、key 属性都相等
4、vnode 属于克隆的虚拟DOM或者是只渲染一次的组件(v-once)
如果满足以上四点,那就赋值一下componentInstance属性就return了,说明整个组件没有任何变化,还在之前的实例。

 let i
 const data = vnode.data
 if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
   i(oldVnode, vnode)
 }

首先第一了一个data 常量,data上面一般是定义一下属性节点的,attrs属性、on事件、directives指令、propshook钩子(当vnode是组件的时候会有init, prepatch, insert , destroy四个钩子)。
init 实例化子组件
prepatch 更新子组件
insert 调用子组件的 ’mounted‘生命周期,或者当’keepAlive‘存在的时候触发组件的activated生命周期
destroy调用子组件的 ’destroyed‘生命周期,或者当’keepAlive‘存在的时候触发组件的deactivated生命周期

const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
  for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) // 调用一系列的update函数
    if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}

紧接着就是调用各种更新函数。
updateAttrs // 更新attr属性
updateClass// 更新class属性
updateDOMListeners // 更新绑定事件属性
updateDOMProps // 更新props属性
updateStyle // 更新style属性
update // 如果ref属性存在,根据ref属性进行更新
updateDrectives // 更新Drectives属性

// 是否存在’text‘文本
if (isUndef(vnode.text)) {
  //旧的vnode和新的vnode是否存在子元素
  if (isDef(oldCh) && isDef(ch)) {
      // 如果旧的vnode和新的vnode不相同就调用’updateChildren‘函数更新
       if (oldCh !== ch) {
          updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
       }
      } else if (isDef(ch)) {
        if (process.env.NODE_ENV !== 'production') {
          // 检查是否有重复的key,如果存在重复会提示’warn‘
          checkDuplicateKeys(ch)
        }
        // 如果旧的vnode不存在子集,但是存在’text‘属性,新的vnode存在子集,那就把’Text‘清空。
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        // 添加新的节点
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        // 移除子节点
        removeVnodes(oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {
       // 设置显示文本为空
        nodeOps.setTextContent(elm, '')
      }
    } else if (oldVnode.text !== vnode.text) {
    // 设置显示文本为最新的值
      nodeOps.setTextContent(elm, vnode.text)
}
nodeOps存在以下这些方法:

1、createElement // 创建节点
2、createElementNS // 创建节点
3、createTextNode// 创建文本节点
4、createComment // 创建注释节点
5、insertBefore // 在节点前面插入
6、removeChild // 移除子节点
7、appendChild // 新增子节点
8、parentNode // 获取父节点
9、nextSibling // 下一个节点
10、tagName // 节点名称
11、setTextContent // 设置节点内容
12、setStyleScope // 清空节点属性值

你可能感兴趣的:(vue源码分析(十九)核心函数之patchVnode)