一、工厂函数
1当组件执行_render函数转换成虚拟VNode时遇到组件时会执行createComponent()函数,如下:
function createComponent ( //第4184行 创建组件Vnode
Ctor, //Ctor:组件的构造函数
data, //data:数组
context, //context:Vue实例
children, //child:组件的子节点
tag
) {
if (isUndef(Ctor)) {
return
}
var baseCtor = context.$options._base;
// plain options object: turn it into a constructor
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
var asyncFactory;
if (isUndef(Ctor.cid)) { //如果Ctor.cid为空,那么Ctor就是一个函数,表明这是一个异步组件
asyncFactory = Ctor; //获取异步组件的函数
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context); //执行resolveAsyncComponent()函数
if (Ctor === undefined) { //如果Ctor是个空的,调用该函数返回一个空的注释节点
// 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
)
}
}
/*略*/
return vnode
}
对于一个组件来说,比如Vue.component(component-name,obj|func),组件的值可以是一个对象,也可以是一个函数,如果是对象,则注册时会执行Vue.extend()函数,如下:
if (type === 'component' && isPlainObject(definition)) { //第4866行 注册组件时,如果组件是个对象,则执行Vue.extend()
definition.name = definition.name || id;
definition = this.options._base.extend(definition);
}
去构造子组件的基础构造函数,此时会在构造函数上新增一个cid属性(在4789行),所以我们这里通过cid来判断该组件是否为一个函数。
回到主线,接着执行resolveAsyncComponent()函数,工厂函数相关的如下:
function resolveAsyncComponent ( //第2283行 异步组件 factory:异步组件的函数 baseCtor:大Vue context:当前的Vue实例
factory,
baseCtor,
context
) {
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
if (isDef(factory.resolved)) { //工厂函数异步组件第二次执行这里时会返回factory.resolved
return factory.resolved
}
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
return factory.loadingComp
}
if (isDef(factory.contexts)) {
// already pending
factory.contexts.push(context);
} else {
var contexts = factory.contexts = [context]; //将context作为数组保存到contexts里,也就是当前Vue实例
var sync = true;
var forceRender = function () { //遍历contexts里的所有元素 下一个tick执行到这里
for (var i = 0, l = contexts.length; i < l; i++) { //依次调用该元素的$forceUpdate()方法 该方法会强制渲染一次
contexts[i].$forceUpdate();
}
};
var resolve = once(function (res) { //定义一个resolve函数
// cache resolved
factory.resolved = ensureCtor(res, baseCtor);
// invoke callbacks only if this is not a synchronous resolve
// (async resolves are shimmed as synchronous during SSR)
if (!sync) {
forceRender();
}
});
var reject = once(function (reason) { //定义一个reject函数
"development" !== 'production' && warn(
"Failed to resolve async component: " + (String(factory)) +
(reason ? ("\nReason: " + reason) : '')
);
if (isDef(factory.errorComp)) {
factory.error = true;
forceRender();
}
});
var res = factory(resolve, reject); //执行factory()函数
if (isObject(res)) {
/*高级组件的逻辑*/
}
sync = false;
// return in case resolved synchronously
return factory.loading
? factory.loadingComp
: factory.resolved
}
}
resolveAsyncComponent内部会定义一个resolve和reject函数,然后执行factory()函数,factory()就是我们在main.js里给HelloWorld组件定义的函数,函数内会执行require函数,由于require()是个异步操作,所以resolveAsyncComponent就会返回undefined
回到resolveAsyncComponent,我们给factory()函数的执行下一个断点,如下:
可以看到返回一个undefined,最后resolveAsyncComponent()也会返回undefined,回到createComponent()函数,由于返回的是undefined,则会执行createAsyncPlaceholder()去创建一个注释节点,渲染后对应的DOM节点树如下:
可以看到对于工厂函数来说,组件完全加载时对应的DOM节点是一个注释节点
在下一个tick等require()加载成功后就会执行resolve(res)函数,也就是在resolveAsyncComponent()内定义的resolve函数,
resolve函数会将结果保存到工厂函数的resolved属性里(也就是组件的定义)然后执行的forceRender()函数,也就是上面标记的蓝色的注释对应的代码
再次重新渲染执行到resolveAsyncComponent的时候此时局部变量factory.resolved存在了,就直接返回该变量, 如下:
二、Promise加载
Promise()比较简单,可以认为是工厂函数扩展成语法糖的知识,他主要是可以很好的配合webpack的语法糖,webpack的import的语法糖就是返回一个promise对象,Vue实际上做异步组件也是为了配合Webpack的语法糖来实现Promise()的趋势。
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
Vue.component('HelloWorld',()=>import('./components/HelloWorld'))
new Vue({
router,
render: h => h(App)
}).$mount('#app')
和工厂函数一样,也会执行两次resolveAsyncComponent,下一个tick的逻辑是一样的,不一样的是触发resolve()的逻辑不通,如下:
function resolveAsyncComponent ( //异步组件
factory,
baseCtor,
context
) {
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
if (isDef(factory.resolved)) { //第一次执行到这里时factory.resolved也不存在
return factory.resolved
}
/*略*/
var res = factory(resolve, reject); //我们这里返回一个含有then的对象
if (isObject(res)) {
if (typeof res.then === 'function') { //如果res是一个函数,即Promise()方式加载时
// () => Promise
if (isUndef(factory.resolved)) { //如果factory.resolved不存在
res.then(resolve, reject); //用then方法指定resolve和reject的回调函数
}
} else if (isDef(res.component) && typeof res.component.then === 'function') {
/**/
}
}
sync = false;
// return in case resolved synchronously
return factory.loading
? factory.loadingComp
: factory.resolved
}
}
例子里执行到factory()后返回的res对象如下:
等到加载成功后就会执行resolve了,后面的步骤和工厂函数的流程是一样的。