本文已参与「新人创作礼」活动,一起开启掘金创作之路。
上一节我们已经清楚了Vue.component()执行后的结果了,那注册了的组件构造器是在哪儿使用的呢?接下来我们继续分析:
CreateElement
在页面渲染的过程中,会执行updateComponent的方法,这其中会执行vm._render()方法,生成vnode,其中需要触发createElement方法,这方面不懂的可以参考我的另一篇文章# new Vue()的时候发生了什么,以超简单代码为例,后面我会持续更新。
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
......
let vnode, ns
// tag 就是传入的component的名字
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
// 不是保留标签
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
// 组件进入这个逻辑 resolveAsset就是查找当前实例的components上是否存在组件构造器
} else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component 存在组件构造器,当参数传入createComponent中生成组件vnode
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
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()
}
}
resolveAsset就是查找 vm.$options.components[tag],这样我们就可以拿到这个组件对象,用以生成构造函数。
/**
* Resolve an asset.
* This function is used because child instances need access
* to assets defined in its ancestor chain.
*/
export function resolveAsset (
options: Object,
type: string,
id: string,
warnMissing?: boolean
): any {
/* istanbul ignore if */
// 没有id直接返回
if (typeof id !== 'string') {
return
}
// 获取实例的options.components
const assets = options[type]
// check local registration variations first
// 检查实例中是否存在这个构造器,有则直接返回
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]
// fallback to prototype chain
// 以上都查不到,从原型链查找
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
}
createComponent
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
// baseCtor 是Vue
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
// 创建组件构造器,将Vue上面的components也合并了,可以参考我的另一篇文章,extend
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
// if at this stage it's not a constructor or an async component factory,
// reject.
// 组件构造器不是函数的情况下,报错
if (typeof Ctor !== 'function') {
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${String(Ctor)}`, context)
}
return
}
// async component
// 异步组件的逻辑,跳过
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
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 || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
// 抽象组件逻辑
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// install component management hooks onto the placeholder node
// 安装钩子函数
installComponentHooks(data)
// return a placeholder vnode
// 创建占位符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
)
// Weex specific: invoke recycle-list optimized @render function for
// extracting cell-slot template.
// https://github.com/Hanks10100/weex-native-directive/tree/master/component
/* istanbul ignore if */
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode)
}
// 返回vnode
return vnode
}
拿到了这个组件对象后则进入createComponent函数,这个函数使用Vue.extend()函数进行扩展,拥有了Vue上面的components属性,这样就生成组件vnode,方便之后创建真实DOM节点进行挂载操作了。 组件后续如何patch渲染到页面呢?笔者之后会出文章讲述过程。
局部注册
子组件注册的components就会在子组件构造器的options.components中,所以该类型的组件可以在内部访问局部注册的子组件,在内部能拿到局部组件的构造器。之后的逻辑与全局组件类似。
总结
通过Vue.component注册的组件保存于Vue.options.components中,且通过Vue.extend合并到了各个子组件构造器的options上面,所以使用这个组件的时候就能拿到定义的构造器,从而进行之后的逻辑。局部组件与全局组件的不同之处就在于,局部组件只存在与注册了这个组件的构造器的options.components中,只能在注册了这个组件的内部获取,在其他地方访问时是拿不到子组件的构造函数的。