前言
在开发Vue项目时,大多数人都会用到组件。在父组件中,子组件的加载一般是按照先后顺序加载的,子组件加载后才会加载父组件。举个栗子:
// app.vue
<script setup>
import {onMounted} from 'vue'
import ChildVue from './child.vue'
onMounted(() => {
console.log('app')
})
</script>
<template>
<ChildVue />
</template>
// child.vue
<script setup>
import {onMounted} from 'vue'
onMounted(() => {
console.log('child')
})
</script>
运行结果:
child
app
如果一个页面的子组件很多,由于会先加载子组件,那么父组件可能会出现比较长的白屏等待时间。我们想让child.vue
组件异步加载应该怎么办呢?Vue3提供了一个新增的defineAsyncComponent
方法来实现异步组件。
异步组件
异步组件这个概念并不是Vue3中才有的,Vue2也有异步组件,不同的是Vue2是通过函数来创建。我们在Vue3中创建异步组件也比较简单,主要是通过defineAsyncComponent
方法来创建一个异步组件,然后再需要的时候再使用异步组件即可。defineAsyncComponent
主要有两种方式:
使用Promise加载函数的方式创建
defineAsyncComponent
接收一个返回Promise
的加载函数。我们知道,import
默认导入的模块是静态的,如果我们将import
用于动态导入模块,那么将放回一个Promise,也就是说我们可以在defineAsyncComponent
的加载函数中直接使用import
来动态导入一个模块。
若非必要,请不要滥用动态导入。
// app.vue
<script setup>
import {onMounted, defineAsyncComponent } from 'vue'
const AsyncChild = defineAsyncComponent(() => import('./child.vue'))
onMounted(() => {
console.log('app')
})
</script>
<template>
<AsyncChild />
</template>
我们也可以自己使用new Promise()
来创建异步组件:
// app.vue
<script setup>
import {onMounted, defineAsyncComponent } from 'vue'
import Child from './child.vue'
const AsyncChild = defineAsyncComponent(() => (new Promise((resolve, reject) => resolve(Child))))
onMounted(() => {
console.log('app')
})
</script>
<template>
<AsyncChild />
</template>
两者的运行结果:
app
child
由此我们可以看到,父组件会先于子组件执行,因此此时的子组件已变成了异步组件。
使用对象的方式创建
采用动态导入的方式创建是简单有效的,不过有时候我们有一些特殊要求,比如说在我们想知道异步组件的加载状态,包括加载中、加载失败等,如果是使用动态导入的方式就无法实现此效果,因此我们需要更高级一点的方式来创建:传入一个特殊对象。
语法说明
const AsyncComp = defineAsyncComponent({
// 加载函数,需要返回一个Promise,可以使用动态import的方式,也可以自己new Promise()
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件,该组件会在异步组件加载时显示,如果异步组件加载很快,可能不会出现loading组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件,可以通过Promise的reject来测试
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
语法说明看起来不复杂,我们就来多举几个例子:
首先我们来新建两个加载状态的组件,也挺简单的,就是一句话:
// LoadingComp.vue
<template>
<div>加载中...</div>
</template>
// ErrorComp
<template>
<div>加载出错啦...</div>
</template>
组件加载失败
我们使用
new Promise()
的方式来测试,如果是使用动态导入的方式,笔者还没有找到合适的方式测试// app.vue // app.vue <script setup> import {ref, onMounted, defineAsyncComponent } from 'vue' import LoadingComp from './LoadingComp.vue' import ErrorComp from './ErrorComp.vue' const AsyncChild = defineAsyncComponent({ loader: () => (new Promise((resolve, reject) => reject())), loadingComponent: LoadingComp, delay: 200, errorComponent: ErrorComp, timeout: 2000 }) onMounted(() => { console.log('app') }) let isShowAsyncComp = ref(false) const loader = () => { isShowAsyncComp.value = true } </script> <template> <button @click="loader">加载异步组件</button> <AsyncChild v-if="isShowAsyncComp" /> </template>
运行结果:
点击
加载异步组件
按钮,会出现加载出错啦...
的报错信息,因为我们在loader
加载函数中返回了一个reject()
的Promise组件加载中
既然是异步组件,一般都是那些加载需要耗时的组件,那么我们可以通过
setTimeout
手动来模拟一个耗时加载组件。本次案例还是采用new Promise()
的方式:// app.vue ... const AsyncComp = defineAsyncComponent({ loader: () => (new Promise((resolve, reject) => setTimeout(() => { resolve(directiveVue) }, 1000))), loadingComponent: LoadingComp, delay: 200, errorComponent: ErrorComp, timeout: 2000 })
我们假设异步组件需要耗时1000ms,在此之前应该都是显示加载中,运行结果:
配合Suspense使用
Suspense
是一个实验性功能,并不保证会成为稳定版,如果非得要使用此功能,建议等版本稳定后使用。因此,我们举的例子仅限于如何使用,至于原理就需要自行去官网查看了:Suspense
// app.vue
<!-- <AsyncComp v-if="isShowAsyncComp" /> -->
<Suspense v-if="isShowAsyncComp">
<template #default>
<AsyncComp />
</template>
<template #fallback>
<p>Suspense 加载中...</p>
</template>
</Suspense>
Suspense
只能处理加载中状态和加载异步组件本身,并不能进行错误处理。当然你可以选择借助onErrorCaptured()
钩子来捕获,或者使用defineAsyncComponent
创建异步组件时指定错误组件。
最后再次说明,Suspense
只是实验性功能,如果是在实际项目中,并不建议使用Suspense
。
总结
本文详细介绍了如何创建并使用异步组件,以及处理异步组件的加载状态,简单总结一下:
- 异步组件采用
defineAsyncComponent
方法来创建,需要传入一个返回值为Promise的加载函数 - 创建异步组件的的方式可以有两种方式:Promise、对象
- 异步组件可配合
Suspense
使用,请注意这个目前是实验功能,存在诸多不确定性 - 注意:Vue-Router配置路由时,虽然也可以有类似异步加载机制的方式加载路由组件,但是不应该使用
defineAsyncComponent