vue源码6----update函数

在vue中,_update是最终把vnode节点渲染成真实dom的函数。

它会在首次渲染和数据更新的时候被调用。

这里主要分析首次调用时候做了什么。

_update函数在src/core/instance/lifecycle.js中定义

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

可以看到,_update的核心是调用__patch__

它在web环境中的定义src/platforms/web/runtime/index.js

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

它指向了patch方法,这次的定义在src/platforms/web/runtime/patch.js

export const patch: Function = createPatchFunction({ nodeOps, modules })

patch接收的是createPatchFunction的返回值

这里传入了一个对象,nodeOps是一系列dom操作方法,modules定义了模块的钩子函数实现

createPatchFunction定义在src/core/vdom/patch.js中,这里patch源码较长,用到哪个贴那个吧

最终执行该函数后,返回了一个patch函数,这个函数就赋值给了vm._update里的vm.__patch__

这里只分析首次调用的情况

return function patch (oldVnode, vnode, hydrating, removeOnly) {//1.旧vnode节点,2._render生成的vnode,3.代表是否服务端渲染,4.是给transition-group用的
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      } else {
        if (isRealElement) {//这里是首次渲染时isRealElement是true
// 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 (process.env.NODE_ENV !== 'production') { 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.' ) } } // either not server-rendered, or hydration failed. // create an empty node and replace it oldVnode = emptyNodeAt(oldVnode)//这里调用emptyNodeAt将oldVnode转换为VNode对象 } // replacing existing element const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) // create new node createElm(//这里实际创建真实的dom对象 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) } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm }

我们直接看createElm

function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
    if (isDef(vnode.elm) && isDef(ownerArray)) {
      // This vnode was used in a previous render!
      // now it's used as a new node, overwriting its elm would cause
      // potential patch errors down the road when it's used as an insertion
      // reference node. Instead, we clone the node on-demand before creating
      // associated DOM element for it.
      vnode = ownerArray[index] = cloneVNode(vnode)
    }

    vnode.isRootInsert = !nested // for transition enter check
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {//这里首先会尝试创建组件
      return
    }

    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    if (isDef(tag)) {//这里判断Vnode是否包含tag,并对其合法性进行简单的校验
      if (process.env.NODE_ENV !== 'production') {
        if (data && data.pre) {
          creatingElmInVPre++
        }
        if (isUnknownElement(vnode, creatingElmInVPre)) {
          warn(
            'Unknown custom element: <' + tag + '> - did you ' +
            'register the component correctly? For recursive components, ' +
            'make sure to provide the "name" option.',
            vnode.context
          )
        }
      }
    //调用平台方法创建一个占位符元素
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
      setScope(vnode)

      /* istanbul ignore if */
      if (__WEEX__) {
        // in Weex, the default insertion order is parent-first.
        // List items can be optimized to use children-first insertion
        // with append="tree".
        const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
        if (!appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
        createChildren(vnode, children, insertedVnodeQueue)//调用createChildren创建子元素
        if (appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)//执行所有的create钩子,并push到insertedVnodeQueue中
          }
          insert(parentElm, vnode.elm, refElm)//最后调用insert方法把dom插入到父节点中
        }
      } else {
        createChildren(vnode, children, insertedVnodeQueue)
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        insert(parentElm, vnode.elm, refElm)
      }

      if (process.env.NODE_ENV !== 'production' && data && data.pre) {
        creatingElmInVPre--
      }
    } else if (isTrue(vnode.isComment)) {//如果没传入tag,先判断是不是注释节点,创建注释vnode再插入
      vnode.elm = nodeOps.createComment(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    } else {//文本节点直接插入
      vnode.elm = nodeOps.createTextNode(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    }
  }

 

return function patch (oldVnode, vnode, hydrating, removeOnly) {     if (isUndef(vnode)) {       if (isDef(oldVnode)) invokeDestroyHook(oldVnode)       return     }
    let isInitialPatch = false     const insertedVnodeQueue = []
    if (isUndef(oldVnode)) {       // empty mount (likely as component), create new root element       isInitialPatch = true       createElm(vnode, insertedVnodeQueue)     } else {       const isRealElement = isDef(oldVnode.nodeType)       if (!isRealElement && sameVnode(oldVnode, vnode)) {         // patch existing root node         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           }           if (isTrue(hydrating)) {             if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {               invokeInsertHook(vnode, insertedVnodeQueue, true)               return oldVnode             } else if (process.env.NODE_ENV !== 'production') {               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.'               )             }           }           // either not server-rendered, or hydration failed.           // create an empty node and replace it           oldVnode = emptyNodeAt(oldVnode)         }
        // replacing existing element         const oldElm = oldVnode.elm         const parentElm = nodeOps.parentNode(oldElm)
        // create new node         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)         }       }     }
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)     return vnode.elm   }
上一篇:linux-如何在Awk单行/单命令中包含短函数?


下一篇:virtual dom