准备
vue版本号2.6.12,为方便分析,选择了runtime+compiler版本。
回顾
如果有感兴趣的同学可以看看我之前的源码分析文章,这里呈上链接:《Vue源码分析系列:目录》
createComponent
的定义
createComponent
定义在croe/vdom/create-component.js
中:
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;
}
//此处的_base指向Vue
//_base定义在'global-api/index.js'中,指向Vue
//因为在'core/instance/init.js'中已经进行了options的合并,故此处可以直接使用context.$options._base访问到Vue
const baseCtor = context.$options._base;
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
//获取组件构造器,使之拥有与Vue相同的功能
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);
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
const name = Ctor.options.name || tag;
//生成一个vnode
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);
}
return vnode;
}
函数体有些长,我们一行一行来看,先看第一行:
const baseCtor = context.$options._base;
此处将上下文对象中的$options
中的_base
赋值给baseCtor
,那_base
定义在哪里?_base
定义在global-api/index.js
中,我们找到了这样的一行定义:
Vue.options._base = Vue
由此可以得知,Vue.options._base
指向的是Vue
类,那为何在上下文对象的$options
中也可以访问到这个_base
呢?还记得在_init
中有这样的一段代码吗?
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
);
}
这里对options
进行了合并,所以在createComponent
中可以直接使用context.$options._base
访问到Vue类。
回到createComponent
,继续往下看:
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;
}
如果Ctor
是一个对象,就执行extend
,如果不是一个函数就抛出警告。
Vue.extend
那着个extend
是什么呢,我们去源码里寻找一下这个extend
api,定义在global-api/extend.js
中:
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {};
const Super = this;
const SuperId = Super.cid;
//获取构造器缓存
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
//如果缓存中有构造器就直接取出并返回
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId];
}
定义Super
指向Vue类,获取缓存,如果缓存中有相应的结果,直接返回。
const name = extendOptions.name || Super.options.name;
if (process.env.NODE_ENV !== "production" && name) {
//校验组件名称
validateComponentName(name);
}
const Sub = function VueComponent(options) {
this._init(options);
};
校验组件的名称,定义了一个构造器。
//es5继承
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.cid = cid++;
Sub.options = mergeOptions(Super.options, extendOptions);
Sub["super"] = Super;
ES5的继承方法,使被继承的原型指向父类,构造器指回自身
//缓存构造器
cachedCtors[SuperId] = Sub;
return Sub;
将构造器缓存并返回。
所以Ctor = baseCtor.extend(Ctor);
这里的意思是获取组件的构造器,使之与Vue构造器拥有相同的功能。
回到createComponent
,继续往下看,略过一些非主线代码:
installComponentHooks(data);
// return a placeholder vnode
const name = Ctor.options.name || tag;
//生成一个vnode
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ""}`,
data,
undefined,
undefined,
undefined,
context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
);
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode);
}
return vnode;
安装了组件的hooks,生成了一个VNode,注意此处的VNode的children
是undefined,但是在第六个参数options
中又传入了children
,这是和普通VNode不同的地方,最后返回了这个VNode。
至此,createComponent
阅读完毕。