组件的概念
Vue
组件就是拥有预定义选项的Vue
实例。
组件的注册方式
- 全局组件:通过
Vue.component
方法注册。 - 局部组件:在组件的选项参数的
components
中定义,只能在注册的组件中使用。
Vue.component方法
Vue.component
方法是在Vue
初始化静态方法时和directive
、filter
一起定义的。Vue.component
内部的主要实现是将用户传入的选项参数通过Vue.extend
方法转换成Vue
子类的构造函数,并将其保存到全局的组件中。
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
// Vue.component('comp', { template: '' })
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
// 把组件配置转换为组件的构造函数 this.options._base = Vue
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
// 全局注册,存储资源并赋值
// this.options['components']['comp'] = definition
this.options[type + 's'][id] = definition
return definition
}
}
})
组件的创建过程
在Vue
实例化时,会逐步调用_init --> $mount --> mountComponent --> new Watcher --> updateComponent --> _render --> createElement --> createComponent
。最终调用createComponent
创建组件对应的VNode
。createComponent
执行过程中会将组件的hooks
复制到选项参数的data
中,包括init
、prepatch
、insert
和destroy
。
组件的渲染过程
在创建完组件的VNode
之后,会将VNode
传递给_update
函数,然后在__patch__
函数中将VNode
转化为真实DOM
,并挂载到视图上。调用_update
函数时(注意这里调用_update
方法的是父组件)会调用setActiveInstance
改变activeInstance
的存储的内容,并将上一个activeInstance
的值保存到prevActiveInstance
,并返回一个能够将activeInstance
重置会prevActiveInstance
的函数,此时activeInstance
保存的是父组件的实例。
__patch__
函数中会先调用createElm
,然后在createElm
中再调用createComponent
来创建组件对应的真实DOM
。
在createComponent
中调用创建组件VNode
时传入的init hook
函数来完成组件真实DOM
的创建和挂载。
init
函数中会调用createComponentInstanceForVnode
来创建组件实例,同时将activeInstance
传入createComponentInstanceForVnode
方法。activeInstance
此时存放的是父组件的实例。
createComponentInstanceForVnode
方法中则是通过new
子组件的构造函数来创建组件实例的。在子组件的构造函数中将调用_.init
方法。这里和Vue
的实例化过程是一样的。
const Sub = function VueComponent (options) {
// 调用 _init() 初始化
this._init(options)
}
_.init
方法执行时会调用initLifecycle
,记录父子组件的关系,在这里将会将子组件的实例放到父组件实例的$children
属性中,将父组件实例记录在子组件的$parent
属性中。
createComponentInstanceForVnode
方法成功返回之后并不会去调用$mount
挂载子组件,只是把子组件实例保存在vnode.componentInstance
中,然后继续向下执行createComponent
,在createComponent
中挂载子组件。
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
// 调用 init() 方法,创建和挂载组件实例
// init() 的过程中创建好了组件的真实 DOM,挂载到了 vnode.elm 上
i(vnode, false /* hydrating */)
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
// 调用钩子函数(VNode的钩子函数初始化属性/事件/样式等,组件的钩子函数)
initComponent(vnode, insertedVnodeQueue)
// 把组件对应的 DOM 插入到父元素中
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
两个重要结论
- 组件创建时会先创建父组件再创建子组件。
- 组件挂载时会先挂载子组件再挂载父组件。