异步组件
白菜 2025/3/11 Vue3组件
# 1. 异步组件解决的问题
- 加载出错时要渲染的组件
- 指定
loading组件,以及展示该组件的延迟时间 - 设置加载组件的超时时长
- 重试机制
加载与错误状态
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
# 2. 异步组件的实现
异步组件的使用
<script setup>
import { defineAsyncComponent } from 'vue'
const AdminPage = defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
</script>
<template>
<AdminPage />
</template>
# 封装 defineAsyncComponent
defineAsyncComponent 用于定义一个异步组件,接受一个异步组件加载器作为参数, 返回值是一个包装组件
根据加载器的状态来决定返回的内容,加载完成则渲染该组件,否则渲染一个占位内容。
占位内容通常是一个注释节点,这里使用一个空文本节点。
先实现一个很糙的异步组件
function defineAsyncComponent(loader) {
// 存储异步加载的组件
let innerComp = null
return {
name: 'AsyncComponentWrapper',
setup(){
// 是否加载完成完成
const loaded = ref(false)
loader().then(c => {
innerComp = c
loaded.value = true
})
return () => {
// 加载完成则渲染该组件,否则渲染一个占位内容
return loaded.value ? { type: innerComp } : { type: 'Text', children: '' }
}
}
}
}
# 超时 & Error 组件
既然有超时和其他清况,那我们就接收一个配置对象作为参数
- 设置一个变量来判断是否超时
- 组件开始加载,并且设置一个定时器,如果超过指定的时间,则认为超时。如果组件被卸载,则清除定时器。
- 根据是否加载完成和是否超时来决定具体的渲染内容,加载超时的时候,如果指定了错误组件,则渲染错误组件
- 当错误发生时,需要把错误对象传递出去,以便用户可以自行进行更细粒度的处理
function defineAsyncComponent(options) {
// options 接收一个函数或者一个对象
if(typeof options === 'function'){
// 如果是函数,就把它转化为配置对象
options = { loader: options }
}
const { loader } = options
let innerComp = null
return {
name: 'AsyncComponentWrapper',
setup(){
const loaded = ref(false)
// 是否超时
// const timeout = ref(false)
// 当错误发生时,用来存储错误信息
const error = shallowRef(null)
loader().then(c => {
innerComp = c
loaded.value = true
}).catch(e => {
// 通过 catch 来捕获加载过程中的初五
error.value = e
})
let timer = null
if(options.timeout){
// 指定了超时时长,则开启一个定时器
timer = setTimeout(() => {
// timeout.value = true
const err = new Error(`Async component timed out after ${options.timeout}`)
error.value = err
}, options.timeout)
}
// 组件卸载,清除定时器
onUnmounted(() => clearTimeout(timer))
// 占位内容
const placeholder = {type: Text, children: ''}
return () => {
if(loaded.value){
// 加载完成则渲染该组件
return { type: innerComp }
}
// if(timeout.value){
// // 如果置指定了错误组件,则渲染该组件,否则渲染一个占位内容
// return options.errorComponent ? { type: options.errorComponent } : placeholder
// }
if(error.value && options.errorComponent){
// 只有当错误存在,并且用户配置了 errorComponent 时,才渲染 Error 组件,同时把错误信息 error 作为 props 传递给 errorComponent 组件
return { type: options.errorComponent, props: { error: error.value} }
}
return placeholder
}
}
}
}
配置了错误信息,之前的变量 timeout 已经不再需要了。
# 延迟 & Loading 组件
使用:
delay指定延迟展示loading组件的时长loadingComponent配置的loading组件
实现:
- 定义一个 loading 变量,标记是否正在加载
- 如果配置了
delay,则开启一个定时器,延时结束后设置loading为 true - 无论是否加载完成都要清除定时器,否则可能出现组件已经加载成功,但是仍然展示 loading 组件
- 组件正在加载,并且用户配置了
loadingComponent,则渲染loadingComponent
function defineAsyncComponent(options) {
//...
return {
name: 'AsyncComponentWrapper',
setup(){
// 是否正在加载
const loading = ref(false)
const loadingTimer = null
// 如果指定了延时,则开启一个定时器, 延时结束后设置 loading 为 true
if(options.delay){
loadingTimer = setTimeout(()=>{
loading.value = true
}, options.delay)
}else{
// 没有配置 delay,则直接设置 loading 为 true
loading.value = true
}
loader().then(c => {
innerComp = c
loaded.value = true
}).finally(() => {
loading.value = true
// 清除 loadingTimer
clearTimeout(loadingTimer)
})
return () => {
// 如果异步组件正在加载,并且用户配置了 loadingComponent,则渲染 loadingComponent 组件
if(loaded.value && options.loadingComponent){
return { type: options.loadingComponent }
}
// ...
}
}
}
}
# 重试机制
function defineAsyncComponent(options) {
//...
// 记录重试次数
let retries = 0
// 通过 load 函数来加载异步组件
function load(){
return loader().catch(err => {
if(options.onError){
return new Promise((resolve, reject) => {
// 重试
const retry = () => {
resolve()
retries++
}
// 失败
const fail = () => reject(err)
// 把 retry,fail,retries 传递给用户自定义的 onError
options.onError(retry, fail, retries)
})
}else{
throw err
}
})
}
return {
name: 'AsyncComponentWrapper',
setup(){
const error = shallowRef(null)
// ...
load()
.then(()=>{
loaded.value = true
innerComp = c
})
.catch((err)=>{
error.value = err
})
// ...
}
}
}