Vue源码解析:异步组件的三种写法原理解析

654 阅读3分钟

异步组件的三种写法

1. 工厂函数

Vue内部定义了resolve,reject。把这两个函数作为参数传入工厂函数,以便作为回调函数执行。

2. 动态import

动态import返回的是一个promise实例,promise.then接受resolve,reject函数用以执行后续逻辑。

3. 处理加载状态

同上只不过,这里将component放置于一个对象之中。

createElement

在Vue组件渲染的时候会调用createElement函数,在函数会首先判断是否存在构造函数是否有cid,如果没有,就证明当前渲染的组件是异步组件。

调用resolveAsyncComponent解析异步组件,如果没有返回值,就证明此时组件还没有真正被渲染,需要创建一个文本节点来占位。

resolveAsyncComponent

核心逻辑解析

参数

factory: 工厂函数

baseCtor:父组件实例

factory执行返回结果res

1. 第一种写法,res并没有返回值

factory对象也没有loading属性,所以直接返回factory.resolved,但是此时异步组件没有渲染,因此也是返回undefined。

当返回undefined时,Vue会先渲染一个占位节点。

2. 第二种写法,返回一个promise实例

res.then(resolve, reject)来加入回调函数

此时factory对象同样没有loading/resolved属性,因此也会渲染一个占位节点。

3. 第三种写法,返回一个含有component属性的对象,component的值为promise实例

res.component.then(resolve,reject)

此时,如果第三种写法定义了loadingComp,就返回loading组件,Vue会 渲染此组件。

resolve回调函数逻辑

1. 利用once函数保证resolve只执行一次

2. 为factory添加resolved属性,此属性的值为组件的构造函数,为factory添加此属性是便于缓存,提升性能

3. sync意义:当resolveAsyncComponent函数第一次执行的时候,函数同步执行,最后返回前将sync设置为false。当resolve回调执行的时候,sync仍让为false,此时进入if(!sync)分支,调用forceRender(true),强制渲染异步组件。

reject回调函数逻辑

1. 利用once函数保证resolve只执行一次

2. 如果factory.errorComp存在就渲染此组件**(主要是针对第三种写法)**

forceRender

会调用组件实例强制渲染。$forceUpdate最终会再次调用resolveAsyncComponent函数,此时factory.resolved已经有值了,直接返回即可。

异步函数解析总体流程:

resolveAsyncComponent -> factory.resolved不存在 -> 执行factory -> resolve/reject -> factory.resolved赋值 ->forceRender -> resolveAsyncComponent再次执行 -> 直接返回factory.resolved

其他概念:owner/owners

这里比较难理解的就是owner与owners了。

考虑到factory是一个引用类型。所以存在一种情况,Vue中多次引用同一个factory。

然而,在Vue中,会为每一个实例创建一个vm对象。这些vm对象并不相等。

如下所示:resolveAsyncComponent函数中的owner就是占位符节点对应的Vue实例vm。

由于多个组件可以引用多个factory函数,所以用了一个owners数组来保存这些vm实例。

性能优化:factory.resolved

既然,引用了同一个factory,那么渲染这么多组件的时候,并不用每次都执行一次factory函数。那么Vue就在factory上定义了resolved属性,只要一次执行,那么多个owner/vm都可以取到factory.resolved值,也就提升了性能

最后

异步组件第一种简单地使用了回调函数;第二种使用了promise;第三种在第二种的基础上添加了很多属性如loading组件等,使得用户体验更加完善。

异步组件初次渲染会返回一个占位节点;再次渲染会生成真正的节点,替换原有的占位节点。具体渲染流程如上文。

另一个不好理解的是factory.resolved如何优化。只要理解了factory与vm的关系就迎刃而解了。

详细的注释

如下图所示,略长