通常为了提升性能,例如首屏渲染速度,我们会异步加载组件,Vue
异步加载组件的方式有以下三种:
- 工厂函数
Promise
- 高级异步组件
Vue.component('async-component', function(resolve, reject) => {
require(['./async-component.vue'], resolve)
})
上述代码,加载了一个文件名为 async-component.vue
的单文件组件,将之命名为 asynv-component
,并注册为全局组件。
利用上述写法,vue-loader
会将 async-component.vue
打包成一个独立文件,实现异步加载。
虽然是异步组件,但除了加载顺序比同步组件稍慢外,其他的逻辑和同步组件都是一样的,同样会借助 vue
源码中的 src/core/vdom/create-component/js
文件中的 createComponent
方法进行组件的初始化和渲染,由于是异步加载,所以组件在一开始时是不会同步加载的,而是会暂时同步渲染出一个注释节点作为占位节点,源代码中的逻辑如下:
// src/core/vdom/create-component.js
// 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
)
}
}
// 通过 import 返回一个 promise对象
Vue.component('async-component', () => import('./async-component.vue'))
加载这种写法的异步组件,在源码中的逻辑位于 src/core/vdom/helpers/resolve-async-component.js
:
if (isObject(res)) {
if (typeof res.then === 'function') {
// () => Promise
if (isUndef(factory.resolved)) {
// 加载异步组件
res.then(resolve, reject)
}
}
// ...
这里其实是利用了 webpack
的 import
语法糖能够直接返回一个 promise
的优势,从而实现异步加载,但实际上这种方法除了加载方式与工厂函数不同之外,剩下的逻辑都是一样的。
// index.vue
import Vue from 'vue'
import App from './App.vue'
const LoadingComp = {
template: '<div>loading</div>'
}
const ErrorComp = {
template: '<div>error</div>'
}
const AsyncComp = () => ({
// 真正需要加载的组件,应当是一个 Promise
component: import('./components/HelloWorld.vue'),
// 在加载完成之前进行占位渲染的组件
loading: LoadingComp,
// 出错时渲染的组件
error: ErrorComp,
// 渲染 loading组件前的等待时间(如果超出这个时间真正需要加载的组件还没来得及渲染好,则渲染laoding组件),默认 200
delay: 200,
// 最长等待时间,如果超过此时间,真正需要加载的组件还没渲染好,则渲染 error组件
timeout: 1000
})
Vue.component('helloWOrld', AsyncComp)
// ...
加载这种写法的异步组件,在源码中的逻辑位于 src/core/vdom/helpers/resolve-async-component.js
:
else if (isDef(res.component) && typeof res.component.then === 'function') {
res.component.then(resolve, reject)
if (isDef(res.error)) {
factory.errorComp = ensureCtor(res.error, baseCtor)
}
if (isDef(res.loading)) {
factory.loadingComp = ensureCtor(res.loading, baseCtor)
if (res.delay === 0) {
factory.loading = true
} else {
setTimeout(() => {
if (isUndef(factory.resolved) && isUndef(factory.error)) {
factory.loading = true
forceRender()
}
}, res.delay || 200)
}
}
if (isDef(res.timeout)) {
setTimeout(() => {
if (isUndef(factory.resolved)) {
reject(
process.env.NODE_ENV !== 'production'
? `timeout (${res.timeout}ms)`
: null
)
}
}, res.timeout)
}
}