笔者在上一篇文章中提到过如下内容:
在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方法一共做了几件事情:
- 校验this.$options上面的render函数是否存在,如果不存在则赋默认值,同时在开发环境下进行检测报警告。
- 调用方法callHook(vm, 'beforeMount')
- 初始化方法updateComponent,updateComponent方法内部调用了vm._update(vm._render(), hydrating)
- 初始化Watcher,并把updateComponent方法传入
- 调用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