前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Vue2.6源码(2):$mount方法干了啥

Vue2.6源码(2):$mount方法干了啥

作者头像
杨艺韬
发布2022-09-27 14:13:25
4300
发布2022-09-27 14:13:25
举报
文章被收录于专栏:前端框架源码剖析

笔者在上一篇文章中提到过如下内容:

mount方法执行过程中,会想办法把vue实例所控制的组件等内容转化成DOM并挂载到mount方法的参数所指向的DOM节点上 杨艺韬,公众号:杨艺韬的网络日志浅析Vue初始化过程(基于Vue2.6)

当时受限于篇幅,并未分析$mount方法内的执行流程,需要告诉大家的是。$mount方法内部执行的过程依然非常复杂,难以在一篇文章中详述,所以本文依然只会分析$mount方法的主体流程,至于内部的各个分支逻辑,笔者将在后续的文章中一一进行解析。从宏观到微观,从抽象到具体,是Vue源码分析系列文章的分析方式。

我们先来看看源码中,$mount方法都干了些什么:

代码语言:javascript
复制
//src/platform/web/runtime/index.js
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

让我们来看一看 mountComponent(this, el, hydrating)内部如何运行的:

代码语言:javascript
复制
//src/core/instance/lifecycle.js
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)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

从上面的代码片段中我们其实可以看到mountComponent方法一共做了几件事情:

  1. 校验this.$options上面的render函数是否存在,如果不存在则赋默认值,同时在开发环境下进行检测报警告。
  2. 调用方法callHook(vm, 'beforeMount')
  3. 初始化方法updateComponent,updateComponent方法内部调用了vm._update(vm._render(), hydrating)
  4. 初始化Watcher,并把updateComponent方法传入
  5. 调用callHook(vm,'mounted')

本文先忽略第2和第5件事情,后续会有专门的分析Vue生命周期的文章。同时我们把第4件事情简化成“调用updateComponent”即可,具体Watcher的内容,后续会有专门的关于Vue的响应式原理的文章进行分析。那好,我们整理下思路,现在我们可以简单的认为,mountComponent方法只做了一件事:执行方法vm._update(vm._render(), hydrating)。

大家会发现,vm又有了个_update方法,vm什么时候有的这个方法呢?大家还记不记得我在上一篇文章中为某个代码片段写了如下注释:

代码语言:javascript
复制
//src/core/instance/index.js
/**
 * Vue.prototype._update
 * Vue.prototype.$forceUpdate
 * Vue.prototype.$destroy
 */
lifecycleMixin(Vue)

上面这里的lifecycleMixin(Vue)执行后,Vue.prototype上就多了个_update方法

那我们来看看_update方法里面做了些上面事情:

代码语言:javascript
复制
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.
  }

本文的前提是基于Vue的首次初始化,所以我们可以认为上面这段代码只做了一件事情:

代码语言:javascript
复制
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)

上面这个__patch__方法中的vnode参数是什么,是上文中_update方法传入的一个VNode,那这个VNode是从哪里来的呢?是vm.render方法执行后返回的Node。这里的__patch__方法,实际上就是对把vm所控制的Vnode转化成DOM并替换vm.$el所指向的DOM。至于render方法的执行过程,以及__patch__方法内部是如果将VNode转化成DOM,如何进行替换,我将会在后续的文章中逐步进行分析,敬请期待。

欢迎关注github,内有笔者不断完善的源码注释:

https://github.com/creator-yangyitao/vue2.6-source-code-analyse

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 杨艺韬 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档