vue源码解析patch.js

前言:在vue2.0 中,VNode转换成真正的DOM是通过patch(oldVNode,VNode,hydrating)方法实现的。

源码目录:/vue/src/core/vdom/patch.ts

js 复制代码
  // oldVnode 一个真实的DOM或者一个Vnode对象
  // vnode 一个待替换的Vnode对象
  // hydrating 是否支持服务端渲染
  // removeOnly 
  function patch(oldVnode, vnode, hydrating, removeOnly) {
    if (isUndef(vnode)) { // 如果没有新的Vnode节点
      //执行destroy钩子函数
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }
    // isInitialPatch 是 Vue 实例的一个内部属性,用于标记组件是否进行了首次补丁(patch)
    let isInitialPatch = false 
    // 创建子组件节点,组件vnode会被铺设到这个队列中
    const insertedVnodeQueue: any[] = []
    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      // 空挂载(可能作为组件),创建新的根元素
      isInitialPatch = true
      // 创建组件节点元素
      // createElm 创建新节点,初始化创建函数。
      createElm(vnode, insertedVnodeQueue)
    } else {
      const isRealElement = isDef(oldVnode.nodeType)
      // 如果不是真实的dom,且新旧vnode 同级比较一致
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node
        // patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly):负责找出差异,diff算法,DOM的更新。
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      } else {
      	// 如果是真实的DOM节点。
        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
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
            //服务端渲染
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (__DEV__) {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                  'server-rendered content. This is likely caused by incorrect ' +
                  'HTML markup, for example nesting block-level elements inside ' +
                  '<p>, or missing <tbody>. Bailing hydration and performing ' +
                  'full client-side render.'
              )
            }
          }
          // 不是服务端渲染,或者 hydration 失败,则根据 oldVnode 创建一个 vnode 节点
          // either not server-rendered, or hydration failed.
          // create an empty node and replace it
          oldVnode = emptyNodeAt(oldVnode)
        }

        // replacing existing element
        // 获取老节点真实dom
        const oldElm = oldVnode.elm
        // 获取老节点父级dom
        const parentElm = nodeOps.parentNode(oldElm)

        // create new node
        // 基于新 vnode 创建整棵 DOM 树并插入到 body 元素下
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // 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)
        }
      }
    }
    // 调用insertedVnodeQueue队列中所有子组件的insert钩子
    invokeInsertHook(vnode, , isInitialPatch)
    return vnode.elm
  }

源码逻辑分析

  1. 新节点不存在,旧节点存在,dom移除。
  2. 新节点存在,旧节点不存在,dom新增。
  3. 新旧节点都存在,旧节点不是真实的元素且新旧节点是同一节点,修改(更新)Diff算法。
  4. 新旧节点都存在,旧节点是真实的元素,一般是初始化渲染,旧节点的真实 DOM 也就是传入进来的 vm.$el 对应的元素,比如
    ;这里还有一个情况就是当 vnode.parent 存在,又不是同一节点,进行替换(更新,比如异步组件。
相关推荐
IT_陈寒3 分钟前
Redis 缓存击穿 vs 雪崩:5个实战方案让你的系统稳如磐石
前端·人工智能·后端
daols8813 分钟前
vue表格vxe-table实现表头合并,分组表头自定义合并
前端·vue.js·vxe-table
执行部之龙13 分钟前
js手写——防抖
开发语言·前端·javascript
DEMO派14 分钟前
JavaScript数据存储三剑客:Object、Map与WeakMap完全指南
开发语言·前端·javascript
一拳不是超人15 分钟前
半年AI编程实战总结:从工具到心法,让AI成为你的超能力
前端·人工智能·ai编程
阿杜杜不是阿木木15 分钟前
从0到1构建像Claude Code那样的Agent(二):工具
前端·chrome·agent·ai编程·cluade code
cramer_50h18 分钟前
Python的web开发框架Django再次更新
前端·python·django
weixin_66818 分钟前
Clawith 大模型设计逻辑与前端接口架构分析 -AI分析
前端·人工智能·架构
x-cmd18 分钟前
[x-cmd] Chrome DevTools MCP 更新:支持 coding agent 直接接管当前的浏览器窗口
前端·chrome·ai·agent·chrome devtools·x-cmd·mcp
芭拉拉小魔仙20 分钟前
Vue v-html 中事件绑定失效问题及解决方案
javascript·vue.js·html