为什么需要异步组件?
vue作为单页应用在首页加载时长会遇到加载缓慢问题,我们可以利用异步组件实现只有当页面需要渲染该组件时才进行引入。
使用方式
另外一种可以通过对象配置的方式
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
// ...从服务器获取组件
resolve(/* 获取到的组件 */)
})
})
// ... 像使用其他一般组件一样使用 `AsyncComp`
第二种
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
对象配置的方式
{
name: 'OperationsMorningReport',
component: defineAsyncComponent({
loader: () => import('../report-template/dispatch-abnormal-tmpl.vue'),
delay: 0
})
}
支持的配置内容
interface AsyncComponentOptions {
loader: AsyncComponentLoader
// 自定义加载中组件
loadingComponent?: Component
// 加载错误组件
errorComponent?: Component
// 延迟加载时长
delay?: number
// 设置超时时间
timeout?: number
suspensible?: boolean
// 加载错误回调函数
onError?: (
error: Error,
retry: () => void,
fail: () => void,
attempts: number
) => any
}
举个例子:
<template>
<div>
hello page
<button @click="showSubComponent = true">展示异步组件</button>
<AsyncComp v-if="showSubComponent"/>
<subComponent v-if="showSubComponent"/>
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
const showSubComponent = ref(false)
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
// ...从服务器获取组件
console.log('async component')
resolve({
template: '<div>Async Component</div>'
})
})
})
const subComponent = defineAsyncComponent(() => import('./sub.vue'))
</script>
<style scoped lang="scss"></style>
[图片上传失败...(image-7140b1-1724491438974)]
只有在点了按钮之后才会加载sub.vue
文件
源码学习
异步组件的实现思路:vue源码packages\runtime-core\src\apiAsyncComponent.ts
看defineAsyncComponent
函数的定义,传入source
即需要异步加载的对象,返回一个组件
export function defineAsyncComponent(source){
return defineComponent({})
}
defineAsyncComponent
函数主要分为3部分
- 处理
source
参数 - 加载异步组件
load
函数 - 返回异步组件
defineComponent
如果我们传的source
是个函数,会直接定义loader
,如果是个对象,会将loader
等一系列参数做解构,默认延迟加载时间是200毫秒
export function defineAsyncComponent<
T extends Component = { new (): ComponentPublicInstance },
>(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
if (isFunction(source)) {
source = { loader: source }
}
// 处理参数
const {
loader,
loadingComponent,
errorComponent,
delay = 200,
timeout, // undefined = never times out
suspensible = true,
onError: userOnError,
} = source
let pendingRequest: Promise<ConcreteComponent> | null = null
let resolvedComp: ConcreteComponent | undefined
let retries = 0
// 重试次数
const retry = () => {
retries++
pendingRequest = null
return load()
}
// 加载异步组件
const load = ()=>{}
// 返回组件
return defineComponent() as T
}
什么时候会执行到load
函数加载异步组件呢?入口是在返回的setup
函数中,发现在下面调用了load
函数
setup函数是什么时候调用的?当开始展示异步组件的时候,会调用setup
函数
return defineComponent({
name: 'AsyncComponentWrapper',
__asyncLoader: load,
get __asyncResolved() {
return resolvedComp
},
setup() {
const instance = currentInstance!
// already resolved
if (resolvedComp) {
return () => createInnerComp(resolvedComp!, instance)
}
// 加载失败回调函数
const onError = (err: Error) => {
pendingRequest = null
handleError(
err,
instance,
ErrorCodes.ASYNC_COMPONENT_LOADER,
!errorComponent /* do not throw in dev if user provided error component */,
)
}
const loaded = ref(false)
const error = ref()
const delayed = ref(!!delay)
// 延迟加载
if (delay) {
setTimeout(() => {
delayed.value = false
}, delay)
}
// 超时处理
if (timeout != null) {
setTimeout(() => {
if (!loaded.value && !error.value) {
const err = new Error(
`Async component timed out after ${timeout}ms.`,
)
onError(err)
error.value = err
}
}, timeout)
}
load()
.then(() => {
// 加载成功后添加缓存
loaded.value = true
if (instance.parent && isKeepAlive(instance.parent.vnode)) {
// parent is keep-alive, force update so the loaded component's
// name is taken into account
instance.parent.effect.dirty = true
queueJob(instance.parent.update)
}
})
.catch(err => {
onError(err)
error.value = err
})
return () => {
if (loaded.value && resolvedComp) {
return createInnerComp(resolvedComp, instance)
} else if (error.value && errorComponent) {
return createVNode(errorComponent, {
error: error.value,
})
} else if (loadingComponent && !delayed.value) {
return createVNode(loadingComponent)
}
}
},
})
load
函数,load
函数中执行了loader
,而loader
就是我们传入的异步组件,如果是个函数则执行传入的函数,
对于第一种方式创建的异步组件,直接返回template
配置的内容
[图片上传失败...(image-b4cea0-1724491429558)]
对于import
引入的组件,引用的文件
[图片上传失败...(image-3e4624-1724491424667)]
[图片上传失败...(image-fad894-1724491418325)]
const load = (): Promise<ConcreteComponent> => {
let thisRequest: Promise<ConcreteComponent>
return (
pendingRequest ||
(thisRequest = pendingRequest =
loader()
.catch(err => {
// 异常处理
})
.then((comp: any) => {
if (thisRequest !== pendingRequest && pendingRequest) {
return pendingRequest
}
// interop module default
if (
comp &&
(comp.__esModule || comp[Symbol.toStringTag] === 'Module')
) {
comp = comp.default
}
resolvedComp = comp
return comp
}))
)
}