如何优雅地使用Vue3的异步组件

简介: 在开发Vue项目时,大多数人都会用到组件。在父组件中,子组件的加载一般是按照先后顺序加载的,子组件加载后才会加载父组件。如果一个页面的子组件很多,由于会先加载子组件,那么父组件可能会出现比较长的白屏等待时间。我们想让`child.vue`组件异步加载应该怎么办呢?Vue3

前言

在开发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>
  1. 组件加载失败

    我们使用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

    image-20220613103836272.png

  2. 组件加载中

    既然是异步组件,一般都是那些加载需要耗时的组件,那么我们可以通过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,在此之前应该都是显示加载中,运行结果:

    image-20220613104317179.png

配合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
相关文章
|
2天前
|
JavaScript API 开发者
关于vue3中v-model做了哪些升级 ?
【10月更文挑战第1天】
102 59
|
1天前
|
存储
Vue3组件通讯六种方式
【10月更文挑战第3天】
7 3
|
1天前
|
缓存 JavaScript
|
1天前
|
JavaScript
vue3,使用watch监听props中的数据
【10月更文挑战第3天】
39 2
|
1天前
|
JavaScript API
|
4天前
|
监控 JavaScript 安全
vue3添加pinia
本文介绍了Pinia作为Vue 3的状态管理库的特点,包括其基于Vue 3的Composition API、响应式状态管理、零依赖设计、插件系统、Devtools集成、Tree-shakable特性以及对TypeScript的支持,并详细说明了如何在Vue 3项目中安装和初始化Pinia。
17 0
vue3添加pinia
|
4天前
|
JavaScript
vue3完整教程从入门到精通(新人必学2,搭建项目)
本文介绍了如何在Vue 3项目中安装并验证Element Plus UI框架,包括使用npm安装Element Plus、在main.js中引入并使用该框架,以及在App.vue中添加一个按钮组件来测试Element Plus是否成功安装。
18 0
vue3完整教程从入门到精通(新人必学2,搭建项目)
|
4天前
|
JavaScript Java CDN
vue3完整教程从入门到精通(新人必学1,vue3快速上手)
本文提供了Vue 3从入门到精通的完整教程,涵盖了创建Vue应用、通过CDN使用Vue、定义网站以及使用ES模块构建版本的步骤和示例代码。
22 0
vue3完整教程从入门到精通(新人必学1,vue3快速上手)
|
2天前
|
资源调度 JavaScript PHP
Vue3+ element plus 前后分离admin项目安装教程
Vue3+ element plus 前后分离admin项目安装教程
7 0
|
3天前
|
存储 资源调度 JavaScript
Vite是什么?怎样使用Vite创建Vue3项目?
Vite是什么?怎样使用Vite创建Vue3项目?
10 0