Vue.component('HelloWorld', function(resolve, reject) {
require(['./components/HelloWorld'], function(res) {
resolve(res);
})
})
对于这个异步组件例子都完整加载流程
-
先执行Vue.component 函数,组件的工厂函数会被挂载到Vue.options下面
-
执行new Vue的时候,render app组件,进入createComponent方法,创建组件构造器,再创建app组件占位vnode,将构造器保存在vnode的组件选项中
-
vm实例(当前Vue实例 进行patch,传入app组件占位 vnode
-
因为vnode是个组件vnode,会调用init hook,创建 app组件实例
-
组件实例 _init, 初始化组件属性时,会继承组件构造器的options,其中包含 Vue.options的组件
-
组件实例进行挂载 mountComponent, 调用render函数,这里的render函数其实是vue-loader转换而成的,app的template中包含两个子节点,img 和 helloworld,会先遍历两个子节点调用render函数,转换为vnode
-
当遍历到helloworld时,类似下面判断,进行异步组件的resolve
else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // component vnode = createComponent(Ctor, data, context, children, tag) }
-
createComponent中, 会识别出异步组件(没有cid, 因为传入的是函数,没有经过Vue.extend转换为构造器)。resolve 异步组件, 主要操作是 定义 resolve, reject 函数, 标志 组件的未加载完成状态,然后调用factory 工厂函数加载组件,返回了undefined,说明组件未加载完成,render函数就返回一个空的注释节点
-
两个子节点都转换为vnode之后,才会执行app组件实例的render函数,tag是div(vue-loader识别的),就直接创建了app组件的vnode
-
然后进行app组件实例的patch过程,此时vnode是app实例的渲染vnode,创建真实节点,遍历子节点创建真实节点,对于异步组件的vnode,tag为undefined,且为注释节点,就会创建一个注释节点。
-
app组件实例patch完成,app 占位vnode patch完成,整个vm实例挂载完成
-
此时异步组件加载完成,会调用resolve 方法,此时改变标志位, 先执行factory.resolved = ensureCtor(res, baseCtor)保证能找到异步组件 JS 定义的组件对象,并且如果它是一个普通对象,则调用 Vue.extend 把它转换成一个组件的构造函数。,然后调用 forceUpdate,强制渲染watcher更新(异步组件加载可能引起视图变化),watcher更新时又会调用之前app组件实例的update方法,在render时进入子节点的createComponent时, 异步组件已经加载完成,调用resolveAsyncComponent已经有值了,就会根据构造器创建异步组件的占位vnode,在后续patch的过程中就能挂载上真实的DOM
总结: 异步组件和普通组件的加载流程的主要区别是 在第一次patch 的过程中异步组件是用注释节点去进行占位,在异步组件加载完成时执行回调(本质上其实就是AMD规范),在回调中将拿到的组件对象转为组件构造器存放在工厂函数下,然后forceUpdate中 会 遍历之前收集的vm实例(这里的vm实例就是依赖异步组件的vm实例,在本例子中就是app组件实例),触发update方法,在render时,异步组件已经加载好了,就会直接返回构造器,就可以构造异步组件的占位vnode了,后续的patch过程就和普通节点一样了。