如何优雅地使用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
相关文章
|
5天前
|
前端开发 JavaScript API
基于Vue3+Hooks实现4位随机数和60秒倒计时
本文介绍了如何在Vue3中使用Hooks API来实现生成4位随机数和执行60秒倒计时的功能,并提供了详细的代码示例和运行效果展示。
23 1
基于Vue3+Hooks实现4位随机数和60秒倒计时
|
5天前
|
数据可视化 JavaScript
Vue3项目使用G6可视化组件实现一个树形机构图
在Vue 3项目中使用G6可视化组件库实现树形机构图的构建和展示。
39 1
Vue3项目使用G6可视化组件实现一个树形机构图
|
1天前
|
JSON JavaScript 前端开发
Vue3在工作中使用的一些经验总结
这篇文章是关于Vue 3项目中使用TypeScript的一些经验总结,包括如何配置TSLint进行代码规范和类型检查,以及如何将现有的JavaScript代码迁移到TypeScript的步骤和注意事项。
Vue3在工作中使用的一些经验总结
|
4天前
|
JavaScript 算法 API
Vue 3有哪些新特性
【8月更文挑战第16天】Vue 3有哪些新特性
25 1
|
5天前
|
JavaScript UED
如何在Vue3项目中使用防抖节流技巧
在Vue 3项目中使用防抖和节流技巧以优化组件性能,包括使用`lodash`库和自定义实现这两种方法。
10 0
如何在Vue3项目中使用防抖节流技巧
|
5天前
|
前端开发 JavaScript
基于Vue3实现鼠标按下某个元素进行拖动,实时改变左侧或右侧元素的宽度,以及点击收起或展开的功能
本文介绍了如何在Vue3项目中实现一个鼠标拖动调整元素宽度的功能,并展示了点击按钮收起或展开侧边栏的效果,提供了完整的实现代码和操作演示。
60 0
基于Vue3实现鼠标按下某个元素进行拖动,实时改变左侧或右侧元素的宽度,以及点击收起或展开的功能
|
1天前
|
JavaScript 前端开发 API
Vue3入门
Vue3入门
4 0
|
4天前
|
JavaScript
创建 Vue3 项目
创建 Vue3 项目
11 0
|
5天前
|
JavaScript
在Vue3+ElementPlus项目中使用具有懒加载的el-tree树形控件
在Vue 3和Element Plus项目中实现具有懒加载功能的el-tree树形控件,以优化大数据量时的页面性能。
14 0
|
5天前
|
JavaScript 前端开发
在Vue3+ElementPlus项目中实现一个简单的新增/移除行记录的小组件
在Vue 3和Element Plus项目中创建一个支持新增和移除行记录的简单表格组件。
30 0