创建组件实例
每个 Vue 实例,都是用new Vue(options)
创建而来的,只是应用的根组件实例是用户显式创建的,而根组件实例里的子组件是在渲染过程中隐式创建的。
该章节里仅讨论根组件实例的创建,而子组件实例的创建,我们将在组件patch
的过程中详细说明。
// 用户显式创建 Vue 实例
var vm = new Vue({
// 选项
})
2
3
4
PS:这里的用户
指的是使用Vue.js
的开发者,一般是指前端开发工程师。
Vue.prototype._init
通过new
操作符创建 Vue 实例时,实际上就是调用了实例方法_init
,以完成一些初始化工作。
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
2
3
4
5
6
7
8
// src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
组件实例的初始化工作,主要有:
- 合并组件的
options
选项配置(详见合并配置) - 初始化组件数据
- 生命周期相关数据
- 事件相关数据
- 渲染相关数据
- (调用
beforeCreate
钩子) provide
/inject
相关数据- 状态相关数据(详见状态数据初始化)
- (调用
created
钩子)
- 对于根组件,调用
vm.$mount
将组件挂载到document
上
Vue.prototype.$mount
若是根组件的组件选项对象里存在el
选项,则会在组件数据初始化之后,自动地调用Vue.prototype.$mount
方法将根组件实例挂载到document
上的el
元素上。若是不存在el
选项,将由用户在需要的时候手动调用vm.$mount(el)
将组件实例挂载。
而Vue.prototype.$mount
方法实际上对mountComponent
函数的封装,以适用于 Web 平台的组件实例的挂载。
// src/platforms/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)
}
2
3
4
5
6
7
8
// src/core/instance/lifecycle.js
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// 非生产环境下,对使用 Vue.js 的运行时版本进行警告
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) {
// 非生产环境下,对组件生成 VNode 和 patch 做性能追踪
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, null, 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
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
mountComponent
函数里,主要做了以下工作:
- 调用
beforeMount
钩子 - 定义渲染 Watcher 的表达式
- 创建渲染 Watcher,且 Watcher 实例会首次计算表达式,创建 VNode Tree,进而生成 DOM Tree
- (对于根组件)调用
mounted
钩子 - 返回组件实例
vm
这里的最重要的是渲染 Watcher 的表达式updateComponent
函数。表达式updateComponent
函数里,vm._render()
将执行组件的vm.$options.render
方法创建并返回组件的 VNode Tree。而vm._update()
方法将基于组件的 VNode Tree 生成 DOM Tree,并在特定情况下进行挂载,生成视图。
渲染 Watcher 首次计算表达式时,会做依赖收集工作,这些依赖是组件在模板里或者render
里使用到的响应式属性,一旦这些依赖发生变化,会通知渲染 Watcher 重新计算表达式以更新视图。
Vue.prototype._render
关于 VNode Tree 的创建,请详见创建 VNode Tree一节。
Vue.prototype._update
关于基于 VNode Tree 生成 DOM Tree,请详见Patch一节。