Snabbdom 源码解析 - patchVnode

259 阅读2分钟

「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战

patchVnode

接下来,我们要进行patch函数中最后一个函数patchVnode的代码解析了

作用

patchVnode函数主要的作用是对比新旧两个节点,然后对比两个节点的差异,然后更新到真实DOM上,下面我们先来看这个函数的整体执行过程

image.png

调用

在调用 patchVnode 函数时候,我们先使用了 sameVnode函数判断两个vnode元素是否是相同的元素

if (sameVnode(oldVnode, vnode)) {
  patchVnode(oldVnode, vnode, insertedVnodeQueue)
}

sameVnode函数代码十分的简单

function sameVnode (vnode1: VNode, vnode2: VNode): boolean {
  return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel
}

就是判断新旧两个节点的keysel是否相同

patchVnode函数

  • 参数
    • oldVnode: 旧节点
    • vnode: 新节点
    • insertedVnodeQueue: 收集具有inserted钩子函数的节点
  • 函数解析: 这个函数内部代码比较多,我们先来看一下整体的过程
  1. 触发 perpatchupdate钩子函数
    • 判断用户是否有传入钩子函数,如果用户传入了 prepatch钩子函数,会立即执行
      const hook = vnode.data?.hook
      hook?.prepatch?.(oldVnode, vnode)
      
    • const elm = vnode.elm = oldVnode.elm!把就旧节点的elm属性赋值给新节点的elm属性,然后分别获取新旧vnode的子节点
      const elm = vnode.elm = oldVnode.elm!
      const oldCh = oldVnode.children as VNode[]
      const ch = vnode.children as VNode[]
      
    • 判断新旧子节点是否相同节点
      if (oldVnode === vnode) return
      
    • 新旧子节点不是相同节点时候,且data有值,执行update函数
      if (vnode.data !== undefined) {
        for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
        vnode.data.hook?.update?.(oldVnode, vnode) // 后执行用户传入的钩子函数,是为了让用户的修改事件不被覆盖
      }
      
  2. 真正对比新旧 vnode 差异的地方,当找到差异之后会立即更新真实DOM
    • 判断新节点是否有 text 属性
      if (isUndef(vnode.text)) {
      
    • 判断新旧节点是否有子节点,并更新DOM
       if (isDef(oldCh) && isDef(ch)) {
         if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue)
       } else if (isDef(ch)) {
         if (isDef(oldVnode.text)) api.setTextContent(elm, '')
         addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
       } else if (isDef(oldCh)) {
         removeVnodes(elm, oldCh, 0, oldCh.length - 1)
       } else if (isDef(oldVnode.text)) {
         api.setTextContent(elm, '')
       }
       ```
      - 如果 新节点有 text 属性,比较新旧节点的text属性是否不相同,相同则不进行任何操作
       ```js
         } else if (oldVnode.text !== vnode.text) {
       ```
      - 判断老节点是否有子节点,有的话调用`removeVnodes`移除该节点
        ```js
           if (isDef(oldCh)) {
             removeVnodes(elm, oldCh, 0, oldCh.length - 1)
           }
           api.setTextContent(elm, vnode.text!) // 更新dom元素
         }
         ```
      
  3. 触发 postpatch 钩子函数
    hook?.postpatch?.(oldVnode, vnode)