组件机制的设计,可以让开发者把一个复杂的应用分割成一个个功能独立组件,降低开发的难度的同时,也提供了极好的复用性和可维护性。本文我们一起从源码的角度,了解一下组件的底层实现原理。组件注册时做了什么?组件注册时做了什么?在Vue中使用组件,要做的第一步就是注册。Vue提供了全局注册和局部注册两种方式。全局注册方式如下:
Vue.component('my-component-name', { /* ... */ })

Vue.component('my-component-name', { /* ... */ })
局部注册方式如下:
var ComponentA = { /* ... */ }

new Vue({
el: '#app',
components: {

'component-a': ComponentA
}
})

var ComponentA = { /* ... */ }

new Vue({
el: '#app',
components: {

'component-a': ComponentA
}
})
全局注册的组件,会在任何Vue实例中使用。局部注册的组件,只能在该组件的注册地,也就是注册该组件的Vue实例中使用,甚至Vue实例的子组件中也不能使用。有一定Vue使用经验的小伙伴都了解上面的差异,但是为啥会有这样的差异呢?我们从组件注册的代码实现上进行解释。
// Vue.component的核心代码
// ASSET_TYPES = ['component', 'directive', 'filter']
ASSET_TYPES.forEach(type => {

Vue[type] = function (id, definition

){

if (!definition) {

return this.options[type + 's'][id]

} else {

// 组件注册

if (type === 'component' && isPlainObject(definition)) {

definition.name = definition.name || id

// 如果definition是一个对象,需要调用Vue.extend()转换成函数。Vue.extend会创建一个Vue的子类(组件类),并返回子类的构造函数。

definition = this.options._base.extend(definition)

}



// ...省略其他代码

// 这里很关键,将组件添加到构造函数的选项对象中Vue.options上。

this.options[type + 's'][id] = definition

return definition

}

}
})

// Vue.component的核心代码
// ASSET_TYPES = ['component', 'directive', 'filter']
ASSET_TYPES.forEach(type => {

Vue[type] = function (id, definition

){

if (!definition) {

return this.options[type + 's'][id]

} else {

// 组件注册

if (type === 'component' && isPlainObject(definition)) {

definition.name = definition.name || id

// 如果definition是一个对象,需要调用Vue.extend()转换成函数。Vue.extend会创建一个Vue的子类(组件类),并返回子类的构造函数。

definition = this.options._base.extend(definition)

}



// ...省略其他代码

// 这里很关键,将组件添加到构造函数的选项对象中Vue.options上。

this.options[type + 's'][id] = definition

return definition

}

}
})

// Vue的构造函数
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)


}

// Vue的初始化中进行选项对象的合并
Vue.prototype._init = function (options) {

const vm = this

vm._uid = uid++

vm._isVue = true

// ...省略其他代码

if (options && options._isComponent) {

initInternalComponent(vm, options)

} else {

// 合并vue选项对象,合并构造函数的选项对象和实例中的选项对象

vm.$options = mergeOptions(

resolveConstructorOptions(vm.constructor),

options || {},

vm

)

}

// ...省略其他代码
}

// Vue的构造函数
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)


}

// Vue的初始化中进行选项对象的合并
Vue.prototype._init = function (options) {

const vm = this

vm._uid = uid++

vm._isVue = true

// ...省略其他代码

if (options && options._isComponent) {

initInternalComponent(vm, options)

} else {

// 合并vue选项对象,合并构造函数的选项对象和实例中的选项对象

vm.$options = mergeOptions(

resolveConstructorOptions(vm.constructor),

options || {},

vm

)

}

// ...省略其他代码
}
以上摘取了组件注册的主要代码。可以看到Vue实例的选项对象由Vue的构造函数选项对象和Vue实例的选项对象两部分组成。全局注册的组件,实际上通过Vue.component添加到了Vue构造函数的选项对象 Vue.options.components 上了。Vue 在实例化时(new Vue(options))所指定的选项对象会与构造函数的选项对象合并作为Vue实例最终的选项对象。因此,全局注册的组件在所有的Vue实例中都可以使用,而在Vue实例中局部注册的组件只会影响Vue实例本身。为啥在HTML模板中可以正常使用组件标签?为啥在HTML模板中可以正常使用组件标签?我们知道组件可以跟普通的HTML一样在模板中直接使用。例如:










// 全局注册一个名为 button-counter 的组件
Vue.component('button-counter', {
data: function () {

return {

count: 0

}
},
template: ''
})

// 创建Vue实例
new Vue({

el: '#app'
})

// 全局注册一个名为 button-counter 的组件
Vue.component('button-counter', {
data: function () {

return {

count: 0

}
},
template: ''
})

// 创建Vue实例
new Vue({

el: '#app'
})
那么,当Vue解析到自定义的组件标签时是如何处理的呢?Vue 对组件标签的解析与普通HTML标签的解析一样,不会因为是非 HTML标准的标签而特殊处理。处理过程中第一个不同的地方出现在vnode节点创建时。vue 内部通过_createElement函数实现vnode的创建。
export function _createElement (
context: Component,
tag?: string | Class | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array {

//...省略其他代码

let vnode, ns
if (typeof tag === 'string') {

let Ctor

ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)

// 如果是普通的HTML标签

if (config.isReservedTag(tag)) {

vnode = new VNode(

config.parsePlatformTagName(tag), data, children,

undefined, undefined, context

)

} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {

// 如果是组件标签,e.g. my-custom-tag

vnode = createComponent(Ctor, data, context, children, tag)

} else {

vnode = new VNode(

tag, data, children,

undefined, undefined, context

)

}
} else {

// direct component options / constructor

vnode = createComponent(tag, data, context, children)
}

if (Array.isArray(vnode)) {

return vnode
} else if (isDef(vnode)) {

if (isDef(ns)) applyNS(vnode, ns)

if (isDef(data)) registerDeepBindings(data)

return vnode
} else {

return createEmptyVNode()
}
}

export function _createElement (
context: Component,
tag?: string | Class | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array {

//...省略其他代码

let vnode, ns
if (typeof tag === 'string') {

let Ctor

ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)

// 如果是普通的HTML标签

if (config.isReservedTag(tag)) {

vnode = new VNode(

config.parsePlatformTagName(tag), data, children,

undefined, undefined, context

)

} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {

// 如果是组件标签,e.g. my-custom-tag

vnode = createComponent(Ctor, data, context, children, tag)

} else {

vnode = new VNode(

tag, data, children,

undefined, undefined, context

)

}
} else {

// direct component options / constructor

vnode = createComponent(tag, data, context, children)
}

if (Array.isArray(vnode)) {

return vnode
} else if (isDef(vnode)) {

if (isDef(ns)) applyNS(vnode, ns)

if (isDef(data)) registerDeepBindings(data)

return vnode
} else {

return createEmptyVNode()
}
}
以文中的button-counter组件为例,由于button-counter标签不是合法的HTML标签,不能直接new VNode()创建vnode。Vue 会通过resolveAsset函数检查该标签是否为自定义组件的标签。
export function resolveAsset (
options: Object,
type: string,
id: string,
warnMissing?: boolean
): any {
/* istanbul ignore if */
if (typeof id !== 'string') {

return
}
const assets = options[type]

// 首先检查vue实例本身有无该组件
if (hasOwn(assets, id)) return assets[id]
const camelizedId = camelize(id)
if (hasOwn(assets, camelizedId)) return assets[camelizedId]
const PascalCaseId = capitalize(camelizedId)
if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]

// 如果实例上没有找到,去查找原型链
const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {

warn(

'Failed to resolve ' + type.slice(0, -1) + ': ' + id,

options

)
}
return res
}

export function resolveAsset (
options: Object,
type: string,
id: string,
warnMissing?: boolean
): any {
/* istanbul ignore if */
if (typeof id !== 'string') {

return
}
const assets = options[type]

// 首先检查vue实例本身有无该组件
if (hasOwn(assets, id)) return assets[id]
const camelizedId = camelize(id)
if (hasOwn(assets, camelizedId)) return assets[camelizedId]
const PascalCaseId = capitalize(camelizedId)
if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]

// 如果实例上没有找到,去查找原型链
const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {

warn(

'Failed to resolve ' + type.slice(0, -1) + ': ' + id,

options

)
}
return res
}
button-counter是我们全局注册的组件,显然可以在this.$options.components找到其定义。因此,Vue会执行createComponent函数来生成组件的vnode。
// createComponent
export function createComponent (
Ctor: Class | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array,
tag?: string
): VNode | Array | void {
if (isUndef(Ctor)) {

return
}

// 获取Vue的构造函数
const baseCtor = context.$options._base

// 如果Ctor是一个选项对象,需要使用Vue.extend使用选项对象,创建将组件选项对象转换成一个Vue的子类
if (isObject(Ctor)) {

Ctor = baseCtor.extend(Ctor)
}

// 如果Ctor还不是一个构造函数或者异步组件工厂函数,不再往下执行。
if (typeof Ctor !== 'function') {

if (process.env.NODE_ENV !== 'production') {

warn(`Invalid Component definition: ${String(Ctor)}`, context)

}

return
}

// 异步组件
let asyncFactory
if (isUndef(Ctor.cid)) {

asyncFactory = Ctor

Ctor = resolveAsyncComponent(asyncFactory, baseCtor)

if (Ctor === undefined) {

// return a placeholder node for async component, which is rendered

// as a comment node but preserves all the raw information for the node.

// the information will be used for async server-rendering and hydration.

return createAsyncPlaceholder(

asyncFactory,

data,

context,

children,

tag

)

}
}

data = data || {}

// 重新解析构造函数的选项对象,在组件构造函数创建后,Vue可能会使用全局混入造成构造函数选项对象改变。
resolveConstructorOptions(Ctor)

// 处理组件的v-model
if (isDef(data.model)) {

transformModel(Ctor.options, data)
}

// 提取props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)

// 函数式组件
if (isTrue(Ctor.options.functional)) {

return createFunctionalComponent(Ctor, propsData, data, context, children)
}

const listeners = data.on
data.on = data.nativeOn

if (isTrue(Ctor.options.abstract)) {

const slot = data.slot

data = {}

if (slot) {

data.slot = slot

}
}

// 安装组件hooks
installComponentHooks(data)

// 创建 vnode
const name = Ctor.options.name || tag
const vnode = new VNode(

`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,

data, undefined, undefined, undefined, context,

{ Ctor, propsData, listeners, tag, children },

asyncFactory
)

return vnode
}


// createComponent
export function createComponent (
Ctor: Class | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array,
tag?: string
): VNode | Array | void {
if (isUndef(Ctor)) {

return
}

// 获取Vue的构造函数
const baseCtor = context.$options._base

// 如果Ctor是一个选项对象,需要使用Vue.extend使用选项对象,创建将组件选项对象转换成一个Vue的子类
if (isObject(Ctor)) {

Ctor = baseCtor.extend(Ctor)
}

// 如果Ctor还不是一个构造函数或者异步组件工厂函数,不再往下执行。
if (typeof Ctor !== 'function') {

if (process.env.NODE_ENV !== 'production') {

warn(`Invalid Component definition: ${String(Ctor)}`, context)

}

return
}

// 异步组件
let asyncFactory
if (isUndef(Ctor.cid)) {

asyncFactory = Ctor

Ctor = resolveAsyncComponent(asyncFactory, baseCtor)

if (Ctor === undefined) {

// return a placeholder node for async component, which is rendered

// as a comment node but preserves all the raw information for the node.

// the information will be used for async server-rendering and hydration.

return createAsyncPlaceholder(

asyncFactory,

data,

context,

children,

tag

)

}
}

data = data || {}

// 重新解析构造函数的选项对象,在组件构造函数创建后,Vue可能会使用全局混入造成构造函数选项对象改变。
resolveConstructorOptions(Ctor)

// 处理组件的v-model
if (isDef(data.model)) {

transformModel(Ctor.options, data)
}

// 提取props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)

// 函数式组件
if (isTrue(Ctor.options.functional)) {

return createFunctionalComponent(Ctor, propsData, data, context, children)
}

const listeners = data.on
data.on = data.nativeOn

if (isTrue(Ctor.options.abstract)) {

const slot = data.slot

data = {}

if (slot) {

data.slot = slot

}
}

// 安装组件hooks
installComponentHooks(data)

// 创建 vnode
const name = Ctor.options.name || tag
const vnode = new VNode(

`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,

data, undefined, undefined, undefined, context,

{ Ctor, propsData, listeners, tag, children },

asyncFactory
)

return vnode
}

由于Vue允许通过一个选项对象定义组件,Vue需要使用Vue.extend将组件的选项对象转换成一个构造函数。
/**

* Vue类继承,以Vue的原型为原型创建Vue组件子类。继承实现方式是采用Object.create(),在内部实现中,加入了缓存的机制,避免重复创建子类。

*/
Vue.extend = function (extendOptions: Object): Function {

// extendOptions 是组件的选项对象,与vue所接收的一样

extendOptions = extendOptions || {}

// Super变量保存对父类Vue的引用

const Super = this

// SuperId 保存父类的cid

const SuperId = Super.cid

// 缓存构造函数

const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})

if (cachedCtors[SuperId]) {

return cachedCtors[SuperId]

}


// 获取组件的名字

const name = extendOptions.name || Super.options.name

if (process.env.NODE_ENV !== 'production' && name) {

validateComponentName(name)

}


// 定义组件的构造函数

const Sub = function VueComponent (options) {

this._init(options)

}


// 组件的原型对象指向Vue的选项对象

Sub.prototype = Object.create(Super.prototype)

Sub.prototype.constructor = Sub


// 为组件分配一个cid

Sub.cid = cid++


// 将组件的选项对象与Vue的选项合并

Sub.options = mergeOptions(

Super.options,

extendOptions

)

// 通过super属性指向父类

Sub['super'] = Super



// 将组件实例的props和computed属代理到组件原型对象上,避免每个实例创建的时候重复调用Object.defineProperty。

if (Sub.options.props) {

initProps(Sub)

}


if (Sub.options.computed) {

initComputed(Sub)

}


// 复制父类Vue上的extend/mixin/use等全局方法

Sub.extend = Super.extend

Sub.mixin = Super.mixin

Sub.use = Super.use


// 复制父类Vue上的component、directive、filter等资源注册方法

ASSET_TYPES.forEach(function (type) {

Sub[type] = Super[type]

})


// enable recursive self-lookup

if (name) {

Sub.options.components[name] = Sub

}


// 保存父类Vue的选项对象

Sub.superOptions = Super.options

// 保存组件的选项对象

Sub.extendOptions = extendOptions

// 保存最终的选项对象

Sub.sealedOptions = extend({}, Sub.options)


// 缓存组件的构造函数

cachedCtors[SuperId] = Sub

return Sub
}
}

/**

* Vue类继承,以Vue的原型为原型创建Vue组件子类。继承实现方式是采用Object.create(),在内部实现中,加入了缓存的机制,避免重复创建子类。

*/
Vue.extend = function (extendOptions: Object): Function {

// extendOptions 是组件的选项对象,与vue所接收的一样

extendOptions = extendOptions || {}

// Super变量保存对父类Vue的引用

const Super = this

// SuperId 保存父类的cid

const SuperId = Super.cid

// 缓存构造函数

const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})

if (cachedCtors[SuperId]) {

return cachedCtors[SuperId]

}


// 获取组件的名字

const name = extendOptions.name || Super.options.name

if (process.env.NODE_ENV !== 'production' && name) {

validateComponentName(name)

}


// 定义组件的构造函数

const Sub = function VueComponent (options) {

this._init(options)

}


// 组件的原型对象指向Vue的选项对象

Sub.prototype = Object.create(Super.prototype)

Sub.prototype.constructor = Sub


// 为组件分配一个cid

Sub.cid = cid++


// 将组件的选项对象与Vue的选项合并

Sub.options = mergeOptions(

Super.options,

extendOptions

)

// 通过super属性指向父类

Sub['super'] = Super



// 将组件实例的props和computed属代理到组件原型对象上,避免每个实例创建的时候重复调用Object.defineProperty。

if (Sub.options.props) {

initProps(Sub)

}


if (Sub.options.computed) {

initComputed(Sub)

}


// 复制父类Vue上的extend/mixin/use等全局方法

Sub.extend = Super.extend

Sub.mixin = Super.mixin

Sub.use = Super.use


// 复制父类Vue上的component、directive、filter等资源注册方法

ASSET_TYPES.forEach(function (type) {

Sub[type] = Super[type]

})


// enable recursive self-lookup

if (name) {

Sub.options.components[name] = Sub

}


// 保存父类Vue的选项对象

Sub.superOptions = Super.options

// 保存组件的选项对象

Sub.extendOptions = extendOptions

// 保存最终的选项对象

Sub.sealedOptions = extend({}, Sub.options)


// 缓存组件的构造函数

cachedCtors[SuperId] = Sub

return Sub
}
}
还有一处重要的代码是installComponentHooks(data)。该方法会给组件vnode的data添加组件钩子,这些钩子在组件的不同阶段被调用,例如init钩子在组件patch时会调用。
function installComponentHooks (data: VNodeData) {
const hooks = data.hook || (data.hook = {})
for (let i = 0; i < hooksToMerge.length; i++) {

const key = hooksToMerge[i]

// 外部定义的钩子

const existing = hooks[key]

// 内置的组件vnode钩子

const toMerge = componentVNodeHooks[key]

// 合并钩子

if (existing !== toMerge && !(existing && existing._merged)) {

hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge

}
}
}

// 组件vnode的钩子。
const componentVNodeHooks = {
// 实例化组件
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {

if (

vnode.componentInstance &&

!vnode.componentInstance._isDestroyed &&

vnode.data.keepAlive

) {

// kept-alive components, treat as a patch

const mountedNode: any = vnode // work around flow

componentVNodeHooks.prepatch(mountedNode, mountedNode)

} else {

// 生成组件实例

const child = vnode.componentInstance = createComponentInstanceForVnode(

vnode,

activeInstance

)

// 挂载组件,与vue的$mount一样

child.$mount(hydrating ? vnode.elm : undefined, hydrating)

}
},

prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {

const options = vnode.componentOptions

const child = vnode.componentInstance = oldVnode.componentInstance

updateChildComponent(

child,

options.propsData, // updated props

options.listeners, // updated listeners

vnode, // new parent vnode

options.children // new children

)
},

insert (vnode: MountedComponentVNode) {

const { context, componentInstance } = vnode

if (!componentInstance._isMounted) {

componentInstance._isMounted = true

// 触发组件的mounted钩子

callHook(componentInstance, 'mounted')

}

if (vnode.data.keepAlive) {

if (context._isMounted) {

queueActivatedComponent(componentInstance)

} else {

activateChildComponent(componentInstance, true /* direct */)

}

}
},

destroy (vnode: MountedComponentVNode) {

const { componentInstance } = vnode

if (!componentInstance._isDestroyed) {

if (!vnode.data.keepAlive) {

componentInstance.$destroy()

} else {

deactivateChildComponent(componentInstance, true /* direct */)

}

}
}
}

const hooksToMerge = Object.keys(componentVNodeHooks)


function installComponentHooks (data: VNodeData) {
const hooks = data.hook || (data.hook = {})
for (let i = 0; i < hooksToMerge.length; i++) {

const key = hooksToMerge[i]

// 外部定义的钩子

const existing = hooks[key]

// 内置的组件vnode钩子

const toMerge = componentVNodeHooks[key]

// 合并钩子

if (existing !== toMerge && !(existing && existing._merged)) {

hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge

}
}
}

// 组件vnode的钩子。
const componentVNodeHooks = {
// 实例化组件
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {

if (

vnode.componentInstance &&

!vnode.componentInstance._isDestroyed &&

vnode.data.keepAlive

) {

// kept-alive components, treat as a patch

const mountedNode: any = vnode // work around flow

componentVNodeHooks.prepatch(mountedNode, mountedNode)

} else {

// 生成组件实例

const child = vnode.componentInstance = createComponentInstanceForVnode(

vnode,

activeInstance

)

// 挂载组件,与vue的$mount一样

child.$mount(hydrating ? vnode.elm : undefined, hydrating)

}
},

prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {

const options = vnode.componentOptions

const child = vnode.componentInstance = oldVnode.componentInstance

updateChildComponent(

child,

options.propsData, // updated props

options.listeners, // updated listeners

vnode, // new parent vnode

options.children // new children

)
},

insert (vnode: MountedComponentVNode) {

const { context, componentInstance } = vnode

if (!componentInstance._isMounted) {

componentInstance._isMounted = true

// 触发组件的mounted钩子

callHook(componentInstance, 'mounted')

}

if (vnode.data.keepAlive) {

if (context._isMounted) {

queueActivatedComponent(componentInstance)

} else {

activateChildComponent(componentInstance, true /* direct */)

}

}
},

destroy (vnode: MountedComponentVNode) {

const { componentInstance } = vnode

if (!componentInstance._isDestroyed) {

if (!vnode.data.keepAlive) {

componentInstance.$destroy()

} else {

deactivateChildComponent(componentInstance, true /* direct */)

}

}
}
}

const hooksToMerge = Object.keys(componentVNodeHooks)

最后,与普通HTML标签一样,为组件生成vnode节点:
// 创建 vnode
const vnode = new VNode(

`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,

data, undefined, undefined, undefined, context,

{ Ctor, propsData, listeners, tag, children },

asyncFactory
)

// 创建 vnode
const vnode = new VNode(

`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,

data, undefined, undefined, undefined, context,

{ Ctor, propsData, listeners, tag, children },

asyncFactory
)
组件在patch时对vnode的处理与普通标签有所不同。Vue 如果发现正在patch的vnode是组件,那么调用createComponent方法。
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {

let i = vnode.data

if (isDef(i)) {

const isReactivated = isDef(vnode.componentInstance) && i.keepAlive

// 执行组件钩子中的init钩子,创建组件实例

if (isDef(i = i.hook) && isDef(i = i.init)) {

i(vnode, false /* hydrating */)

}



// init钩子执行后,如果vnode是个子组件,该组件应该创建一个vue子实例,并挂载到DOM元素上。子组件的vnode.elm也设置完成。然后我们只需要返回该DOM元素。

if (isDef(vnode.componentInstance)) {

// 设置vnode.elm

initComponent(vnode, insertedVnodeQueue)

// 将组件的elm插入到父组件的dom节点上

insert(parentElm, vnode.elm, refElm)

if (isTrue(isReactivated)) {

reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)

}

return true

}

}
}

function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {

let i = vnode.data

if (isDef(i)) {

const isReactivated = isDef(vnode.componentInstance) && i.keepAlive

// 执行组件钩子中的init钩子,创建组件实例

if (isDef(i = i.hook) && isDef(i = i.init)) {

i(vnode, false /* hydrating */)

}



// init钩子执行后,如果vnode是个子组件,该组件应该创建一个vue子实例,并挂载到DOM元素上。子组件的vnode.elm也设置完成。然后我们只需要返回该DOM元素。

if (isDef(vnode.componentInstance)) {

// 设置vnode.elm

initComponent(vnode, insertedVnodeQueue)

// 将组件的elm插入到父组件的dom节点上

insert(parentElm, vnode.elm, refElm)

if (isTrue(isReactivated)) {

reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)

}

return true

}

}
}
createComponent会调用组件vnode的data对象上定义的init钩子方法,创建组件实例。现在我们回过头来看下init钩子的代码:
// ... 省略其他代码
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {

if (

vnode.componentInstance &&

!vnode.componentInstance._isDestroyed &&

vnode.data.keepAlive

) {

// kept-alive components, treat as a patch

const mountedNode: any = vnode // work around flow

componentVNodeHooks.prepatch(mountedNode, mountedNode)

} else {

// 生成组件实例

const child = vnode.componentInstance = createComponentInstanceForVnode(

vnode,

activeInstance

)

// 挂载组件,与vue的$mount一样

child.$mount(hydrating ? vnode.elm : undefined, hydrating)

}
}
// ...省略其他代码

// ... 省略其他代码
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {

if (

vnode.componentInstance &&

!vnode.componentInstance._isDestroyed &&

vnode.data.keepAlive

) {

// kept-alive components, treat as a patch

const mountedNode: any = vnode // work around flow

componentVNodeHooks.prepatch(mountedNode, mountedNode)

} else {

// 生成组件实例

const child = vnode.componentInstance = createComponentInstanceForVnode(

vnode,

activeInstance

)

// 挂载组件,与vue的$mount一样

child.$mount(hydrating ? vnode.elm : undefined, hydrating)

}
}
// ...省略其他代码
由于组件是初次创建,因此init钩子会调用createComponentInstanceForVnode创建一个组件实例,并赋值给vnode.componentInstance。
export function createComponentInstanceForVnode (
vnode: any,
parent: any,
): Component {
// 内部组件选项
const options: InternalComponentOptions = {

// 标记是否是组件

_isComponent: true,

// 父Vnode

_parentVnode: vnode,

// 父Vue实例

parent
}
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate
if (isDef(inlineTemplate)) {

options.render = inlineTemplate.render

options.staticRenderFns = inlineTemplate.staticRenderFns
}
// new 一个组件实例。组件实例化 与 new Vue() 执行的过程相同。
return new vnode.componentOptions.Ctor(options)
}

export function createComponentInstanceForVnode (
vnode: any,
parent: any,
): Component {
// 内部组件选项
const options: InternalComponentOptions = {

// 标记是否是组件

_isComponent: true,

// 父Vnode

_parentVnode: vnode,

// 父Vue实例

parent
}
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate
if (isDef(inlineTemplate)) {

options.render = inlineTemplate.render

options.staticRenderFns = inlineTemplate.staticRenderFns
}
// new 一个组件实例。组件实例化 与 new Vue() 执行的过程相同。
return new vnode.componentOptions.Ctor(options)
}
createComponentInstanceForVnode 中会执行 new vnode.componentOptions.Ctor(options)。由前面我们在创建组件vnode时可知,vnode.componentOptions的值是一个对象:{ Ctor, propsData, listeners, tag, children },其中包含了组件的构造函数Ctor。因此 new vnode.componentOptions.Ctor(options)等价于new VueComponent(options)。
// 生成组件实例
const child = vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance)
// 挂载组件,与vue的$mount一样
child.$mount(hydrating ? vnode.elm : undefined, hydrating)

// 生成组件实例
const child = vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance)
// 挂载组件,与vue的$mount一样
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
等价于:
new VueComponent(options).$mount(hydrating ? vnode.elm : undefined, hydrating)

new VueComponent(options).$mount(hydrating ? vnode.elm : undefined, hydrating)
这段代码想必大家都很熟悉了,是组件初始化和挂载的过程。组件的初始化和挂载与在前文中所介绍Vue初始化和挂载过程相同,因此不再展开说明。大致的过程就是创建了一个组件实例并挂载后。使用initComponent将组件实例的$el设置为vnode.elm的值。最后,调用insert将组件实例的DOM根节点插入其父节点。然后就完成了组件的处理。总结总结通过对组件底层实现的分析,我们可以知道,每个组件都是一个VueComponent实例,而VueComponent又是继承自Vue。每个组件实例独立维护自己的状态、模板的解析、DOM的创建和更新。篇幅有限,文中只分析了基本的组件的注册解析过程,未对异步组件、keep-alive等做分析。等后面再慢慢补上。以上就是详解vue 组件的实现原理的详细内容,关于vue组件的资料请关注其它相关文章!