• vue源码学习_update


    vue之_update()

    前言

    vue在调用$mount方法的时候会调用mountComponent ,通过vm.render可以得到vnode,得到vnode之后,在mount的时,会调用_update方法,那么_update方法主要是用来做些什么事情呢?

    export function mountComponent (
      vm: Component,
      el: ?Element,
      hydrating?: boolean
    ): Component {
      vm.$el = el
      if (!vm.$options.render) {
        vm.$options.render = createEmptyVNode
        if (process.env.NODE_ENV !== 'production') {
          /* istanbul ignore if */
          if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
            vm.$options.el || el) {
            warn(
              'You are using the runtime-only build of Vue where the template ' +
              'compiler is not available. Either pre-compile the templates into ' +
              'render functions, or use the compiler-included build.',
              vm
            )
          } else {
            warn(
              'Failed to mount component: template or render function not defined.',
              vm
            )
          }
        }
      }
      callHook(vm, 'beforeMount')
    
      let updateComponent
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        updateComponent = () => {
          const name = vm._name
          const id = vm._uid
          const startTag = `vue-perf-start:${id}`
          const endTag = `vue-perf-end:${id}`
    
          mark(startTag)
          const vnode = vm._render()
          mark(endTag)
          measure(`vue ${name} render`, startTag, endTag)
    
          mark(startTag)
          vm._update(vnode, hydrating)
          mark(endTag)
          measure(`vue ${name} patch`, startTag, endTag)
        }
      } else {
        updateComponent = () => {
          vm._update(vm._render(), hydrating)
        }
      }
    

    _update方法

    Vue的_update方法是实例上的一个私有方法,主要作用是把VNode渲染成真实的dom,它在首次渲染和数据更新的时候被调用。在数据更新的时候会发生VNode和旧VNode对比,获取差异更新视图,我们常说的diff就是发生在此过程中。

    Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
        const vm: Component = this
        // 页面的挂载点,真实的元素
        const prevEl = vm.$el
        // 老VNode
        const prevVnode = vm._vnode
        const restoreActiveInstance = setActiveInstance(vm)
        // 新VNode
        vm._vnode = vnode
        // Vue.prototype.__patch__ is injected in entry points
        // based on the rendering backend used.
        if (!prevVnode) {
           // 老VNode不存在,表示首次渲染,即初始化页面时走这里
          // 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.
      }
    

    __patch__方法

    // src/platforms/web/runtime/index.js
    // web 平台的patch函数,不同平台的定义不同
    Vue.prototype.__patch__ = inBrowser ? patch : noop
    

    patch方法

    // src/platforms/web/runtime/patch.js
    import * as nodeOps from 'web/runtime/node-ops'
    import { createPatchFunction } from 'core/vdom/patch'
    import baseModules from 'core/vdom/modules/index'
    import platformModules from 'web/runtime/modules/index'
    
    // the directive module should be applied last, after all
    // built-in modules have been applied.
    const modules = platformModules.concat(baseModules)
    
    // 传入平台特有的一些操作,然后返回一个 patch 函数
    export const patch: Function = createPatchFunction({ nodeOps, modules })
    

    patchcreatePatchFunction的返回值,这里传入了一个对象,nodeOps封装了一系列相关平台DOM的一些操作方法,modules表示平台特有的一些操作,比如attr,class,style,event等,还有核心的directiveref,它们会想外暴露一些特有的方法。这里不做赘述。

    createPatchFunction

    // src/core/vdom/patch.js
    const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
    /**
     * 传入相关平台一些功能操作,最后返回 patch 函数
     */
    export function createPatchFunction (backend) {
      let i, j
      const cbs = {}
    
      const { modules, nodeOps } = backend
      /**
       * hooks = ['create', 'activate', 'update', 'remove', 'destroy']
       * 遍历这些钩子,然后从 modules 的各个模块中找到相应的方法,比如:directives 中的 create、update、destroy 方法
       *  cbs[hook] = [hook 方法],比如: cbs.create = [fn1, fn2, ...]
       */
      for (i = 0; i < hooks.length; ++i) {
        // 比如 cbs.create = []
        cbs[hooks[i]] = []
        for (j = 0; j < modules.length; ++j) {
          if (isDef(modules[j][hooks[i]])) {
            // 遍历各个 modules,找出各个 module 中的 create 方法,然后添加到 cbs.create 数组中
            cbs[hooks[i]].push(modules[j][hooks[i]])
          }
        }
      }
      
      /**
       * ...这里定义了一大堆辅助函数,下面用到了捡重点看一下,不一一描述了。
       */
      
      /**
       * @param oldVnode  旧节点,可以不存在或是一个 DOM 对象
       * @param vnode  新节点,_render 返回的节点
       * @param hydrating  是否是服务端渲染
       * @param removeOnly  是给 transition-group 用的
       */
      return function patch (oldVnode, vnode, hydrating, removeOnly) {
        // 新节点不存在,旧节点存在,表示移除,销毁旧节点。
        if (isUndef(vnode)) {
          if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
          return
        }
        let isInitialPatch = false
        const insertedVnodeQueue = []
        if (isUndef(oldVnode)) {
          // 新节点存在,旧节点不存在,表示新增。
          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 转换成 VNode 对象.
              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
      }
    }
    
    

    createPatchFunction返回的patch方法来看,主要分为这么几种情况做处理:

    • 新节点不存在,旧节点存在,表示移除。
    • 新节点存在,旧节点不存在,表示新增
    • 新旧节点都存在,旧节点不是真实的元素且新旧节点是同一节点,表示修改(更新)。
    • 新旧节点都存在,旧节点是真实的元素,一般是初始化渲染,旧节点的真实DOM也就是传入进来的vm.$el对应的元素,比如<div id="app">;这里还有一个情况就是当vnode.parent存在,又不是同一节点,表示替换(更新),比如异步组件

    移除节点

    // src/core/vdom/patch.js
    /**
    * 执行组件的destroy钩子,即执行$destroy方法
    * 执行组件各个模块(style、class、directive等)的destroy方法
    * 如果vnode还存在子节点,则递归调用
    */
    function invokeDestroyHook (vnode) {
      let i, j
      const data = vnode.data
      if(isDef(data)) {
        if(isDef(i =data.hook) && isDef(i = i.destroy)) i(vnode)
        for(i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
      }
      if(isDef(i = vnode.children)) {
        for (j = 0; j < vnode.children.length;++j) {
          invokeDestroyHook(vnode.children[j])
        }
      }
    }
    

    初始化

    createPatchFunction中的patch方法内,由于传入的vm.$el,实际上是一个真实的DOM,所以isRealElement为true,(服务端渲染先跳过)然后通过emptyNodeAtoldVnode转换成VNode对象。

    emptyNodeAt

    // src/core/vdom/patch.js
    function emptyNodeAt (elm) {
      return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
    }
    
    

    createElm

    然后调用createElm方法,这个是重点:

    // src/core/vdom/patch.js
    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
      /**
       * 1、如果 vnode 是一个组件,则执行 init 钩子,创建组件实例并挂载,
       * 然后为组件执行各个模块的 create 钩子
       *   如果组件被 keep-alive 包裹,则激活组件
       * 2、如果是一个普通元素,则什么也不做
       */
      if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
        return
      }
      // 获取 Data 对象
      const data = vnode.data
      // 获取子VNode
      const children = vnode.children
      // 判断 tag
      const tag = vnode.tag
      if (isDef(tag)) {
       // 如有有 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)
          if (appendAsTree) {
            if (isDef(data)) {
              invokeCreateHooks(vnode, insertedVnodeQueue)
            }
            insert(parentElm, vnode.elm, refElm)
          }
        } 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)) {
        // 注释节点,创建注释节点并插入父节点
        vnode.elm = nodeOps.createComment(vnode.text)
        insert(parentElm, vnode.elm, refElm)
      } else {
        // 文本节点,创建文本节点并插入父节点
        vnode.elm = nodeOps.createTextNode(vnode.text)
        insert(parentElm, vnode.elm, refElm)
      }
    }
    

    createElm的作用就是通过VNode创建真实的DOM插入到父节点中,它首先会执行createComponent方法,这个方法是用来创建子组件的,在初始化的时候会返回false,后面组件新增的时候再看这个方法。然后会对判断vnode.tag,如果存在的话,会走入下面的逻辑。

    vnode.elm = vnode.ns
    ? nodeOps.createElementNS(vnode.ns, tag)
    : nodeOps.createElement(tag, vnode)
    
    ``
  • 相关阅读:
    JVM 基础知识
    Maven 学习
    Java 中 split() 方法
    JDK、JRE、JVM三者联系与区别
    Effective C++ 笔记 —— Item 5: Know what functions C++ silently writes and calls
    Effective C++ 笔记 —— Item 4: Make sure that objects are initialized before they’re used
    Effective C++ 笔记 —— Item 3: Use const whenever possible
    Effective C++ 笔记 —— Item 2: Prefer consts, enums, and inlines to #defines
    Idea社区版使用插件完成正常开发
    DNS配置
  • 原文地址:https://www.cnblogs.com/dehenliu/p/16147604.html
Copyright © 2020-2023  润新知