一、异步组件 基本用法
相对于正常组件来讲,异步组件,顾名思义,就是组件里有异步操作。
(可能说法不准确,但是也是可以这么理解的。
定义一个正常的vue组件,我们用defineComponent ( { } ),此处为选项式API。但随着Vue3成为默认版本,<script setup>出现,使用组合式API编程时,不再显示使用这个API。
# Vue提供给我们定义异步组件的方式,下面来看看怎么定义异步组件吧!
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
resolve(/* 异步操作得到的结果 */)
})
})
// ... 像使用其他一般组件一样使用 `AsyncComp`
其中 defineAsyncComponent 代替了 defineComponent ,而参数变成了一个函数,而不是之前的配置对象。
首先来说这个函数,它返回一个Promise对象(解决异步问题的利剑,屡试不爽),把异步操作放在new Promise()里面。别忘记了,还能.then链式调用!
而在官方的文档里,提到了一种返回Promise对象的表达式,那就是ES 模块动态导入,多数情况下,import 和 defineAsyncComponent 搭配使用,效果不错。
试想,一个组件A在页面加载时,先不渲染,而当页面需要它时,再渲染。这样做的好处很明显,最直观的就是提高了首页加载速度。但是需要注意的点是组件A本身不是一个异步组件(A里面没有异步操作),而他本身是需要异步加载再渲染的,这时候我们定义一个异步组件asyncA,将加载组件A的操作,封装在刚才提到的Promise里,是不是就解决问题了呢!
# 异步组件作外壳,封装组件A加载的异步操作代码如下
import { defineAsyncComponent } from 'vue'
const asyncA= defineAsyncComponent(() =>
import('./components/A.vue')
)
# 也可以直接在父组件中直接定义它们:
// 父组件中定义异步组件 <asyncA/>
<script setup>
import { defineAsyncComponent } from 'vue'
const asyncA= defineAsyncComponent(() =>
import('./components/A.vue')
)
</script>
<template>
<asyncA/>
</template>
前面提到“壳”这一说法,那我们接下来就探讨一下,为什么把异步组件称之为壳。
我们定义的异步组件asyncA,与传统组件不同的是,asyncA会在页面需要它时才调用加载内部的实际组件,这意味着asyncA不是一个会在页面中渲染的组件,真正渲染的是其内部加载的组件,即asyncA只是用于异步加载。而且它将接受到的props和插槽传给内部组件,所以你可以使用这个异步的包装组件无缝替换原始组件,同时实现延迟加载。
二、defineAsyncComponent 的参数对象的高级选项
在之前,defineAsyncComponent ()的参数是一个返回Promise对象的函数,而这不是唯一的形式。由于异步操作不可避免的涉及加载和错误状态,所以我们需要一些办法来处理这些问题!
# 那就是配置高级选项对象作为参数
const AsyncComp = defineAsyncComponent(
{
// 加载函数
loader: () => import('./A.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制(ms),并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
}
)
没错,我们之前定义的函数就作为了 loader 的值!
🎉加载组件 loadingComponent:它将在异步组件(壳)的内部组件A进行加载时显示(此时A还未成功加载)。
🎉延迟 delay:在加载组件显示之前有一个默认的 200ms 延迟——这是因为在网络状况较好时,你加载A的过程很快快快,也就意味着你 loadingComponent 还没显示几毫秒,就立马替换了已经加载好的最终组件A,由于替换太快可能产生闪烁,反而影响用户感受,所以这里设置一下延迟,等 loadingComponent 显示200ms后才可替换(无论A加载完成与否)。
🎉报错组件 errorComponent:在加载器 loader 返回的 Promise 抛错时被渲染,以应对 loader加载不成功的情况。
🎉超时时间 timeout:请求加载A有时候需要它在一定时间内完成,你可以指定一个限定时间,如果3s内没有加载成功,那么也会渲染报错组件。
三、搭配Suspense使用
异步组件可以搭配内置的 <Suspense> 组件一起使用。
🍿 官方文档说明: <Suspense> 这是一项实验性功能,不一定最终成为稳定功能,即相关API会发生变化,如果哪一天API报错,那可能就是官方更新了。
<Suspense> 是一个内置组件,用来在组件树中协调对异步依赖的处理,它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染同一个加载状态。
<Suspense>
└─ <A>
├─ <B1>
│ └─ <C1>(组件有异步的 setup())
└─ <B2>
├─ <C2> (异步组件)
└─ <C3>(异步组件)
</Suspense>
试想上面这样的一种组件树层级关系,对于上面一句话的解释就是:
协调对异步依赖的处理:由于C1、C2、C3,都是异步组件,这颗组件树需要等待这些异步组件的异步操作完成后才能成功渲染,也就是说这三个异步组件是这颗组件树的异步依赖。
上层等待下层、同一个:
上面我们提到过异步组件有加载组件、最终组件和报错组件间的替换,如果没有<Suspense> ,则它们每个都需要处理自己的加载、报错和完成状态。
在一种情况下,页面上会有三个加载组件,并且可能在某一时刻,一个异步组件加载成功,完成了加载组件和最终组件的替换,其他两个显示加载组件,这是很混乱、糟糕的情况。
所以使用<Suspense>组件,将组件树的这些异步依赖嵌套起来,等待整个组件树中的各个异步依赖获取结果时,在顶层展示出同一个加载中或加载失败的状态,也就是,在多异步依赖,只有异步的操作全部完成后才会在顶层展示出加载成功状态,否则只要有一个异步没有完成,顶层就会一直处于加载中状态,这样我们就实现了上层等待下层。
这里的实现和Promise. all( )十分相像,统一了多个异步的状态,也实现了上层等到下层的效果。
🍿 这里再详细说一下异步依赖的具体含义:
1、上面我们提到的异步组件,就是一种组件树的异步依赖。
这是相对于组件树的概念,正是因为组件树的渲染“依赖”于这些异步组件的异步操作完成,所以才叫 异 步 依 赖!
2、带有异步setup( ) 的组件,也包含了<script srtup>中有await 表达式的组件。 示例如下:
// *****第一种 setup
export default {
async setup() {
const res = await fetch(...)
const posts = await res.json()
return {
posts
}
}
}
// *****第二种 setup
<script setup>
const res = await fetch(...)
const posts = await res.json()
</script>
<template>
{{ posts }}
</template>
到此,以上内容,完全是围绕第三小节开头的那一句“<Suspense> 是一个内置组件,用来在组件树中协调对异步依赖的处理.......”进行的。
🎉接下来我们谈一下“大一统”问题。
前面提到,异步组件就会被当作这个<Suspense> 的一个异步依赖。在这种情况下,加载状态是由 <Suspense> 控制,而该组件自己的加载、报错、延时和超时等选项都将被忽略。(让组件始终自己控制其加载状态,可以在异步组件选项中指定 suspensible : false)
1、<Suspense> 控制多个异步依赖的加载中状态
<Suspense>组件有两个插槽:#default 和 #fallback。两个插槽都只允许一个直接子节点。两个插槽会在合适的时机(完成、挂起状态)进行渲染
<Suspense>
<!-- 具有深层异步依赖的组件 -->
<!-- #default 插槽 -->
<Dashboard />
<!-- 另一种写法 -->
/*
<template #default>
<Dashboard />
</template>
*/
<!-- 在 #fallback 插槽中显示 “正在加载中” -->
<template #fallback>
Loading...
</template>
</Suspense>
在初始渲染时,<Suspense>将在内存中渲染其 #default 默认插槽中的内容。但是随着程序的执行,在这一过程中,遇到任何异步依赖,就会进去挂起状态。在挂起状态期间,<Suspense>展示 #fallback 插槽里的内容。当所有异步依赖完成后,<Suspense>进入完成状态,展示 #default 插槽里的内容。
也就是说,初始渲染时,在<Suspense> 嵌套的组件树下,一旦有异步依赖,一开始会进入挂起状态,显示 #fallback 插槽里的内容。而在内存中,#default 内容一直在渲染,一旦渲染完成,则会进入完成状态,将用 #default 内容替换掉 #fallback 内容。
✨如果在初次渲染时没有遇到异步依赖,<Suspense> 会直接进入完成状态,渲染其 #default 默认插槽中的内容。
!!!但是进入完成状态后,要是再想回到挂起状态,就不是那么容易了 !!!
只有当默认插槽的根节点(如下图C1、C2、C3)被替换(D1、D2...)时,<Suspense> 才会回到挂起状态。回退到挂起状态时,后备插槽内容不会立即展示出来。相反,<Suspense>在等待新内容和异步依赖完成时,会展示之前 #default 插槽的内容(A、B、C)。这个行为是可控的,timeout 属性可以对其进行配置:在等待渲染新内容耗时超过 timeout 之后(此时显示默认插槽内容),<Suspense> 将会切换为展示后备内容。若 timeout 值为 0
将导致回退到挂起状态那一刻,立即显示后备插槽内容。
<Suspense>
└─ <A>
├─ <B1>
│ └─ <C1>(组件有异步的 setup())
└─ <B2>
├─ <C2> (异步组件)
└─ <C3>(异步组件)
</Suspense>
2、<Suspense>组件触发的事件
<Suspense>触发三个事件:pending、resolve 和 fallback。
Pending:进入挂起状态时触发。
Resolve:#default 完成新内容获取时触发。
Fallback:显示 #fallback 插槽内容时触发。
3、<Suspense>错误处理
<Suspense>由于还在实验性阶段,本身不提供错误处理,不过可以使用 errorCaptured 选项或者 onErrorCaptured() 钩子,在使用到<Suspense>的父组件中捕获和处理异步错误。
-----------------------------------------完结撒花---------------------------------------
到此为止,整篇文章已结束!
银鞍照白马,飒沓如流星!
希望你有所收获!
✨相应的Vue官方文档:异步组件 | Vue.js