首先需要理解一下什么是异步组件
在 vue 中,当我们注册全局组件或者局部组件时,他们都是同步的被立即解析并加载的,这意味着在程序初始化时,所有组件都会通过网络被下载到内存中,并且在内存中占用一定的资源,预加载所有组件的话,会将页面的初始化加载时间和性能降低,尤其是在移动端上,为了避免这种情况,vue 才提供了异步组件
为什么要用异步组件?他有什么好处?
- 异步组件可以将我们的组件分开打包,按需加载,这样可以减轻初始化页面时的加载时间和减少资源浪费
- 当组件需要和路由配合使用时,异步组件也能帮助我们实现按需加载和动态导入,这样可以在调用组件时再切换路由实现动态加载组件,有助于提高应用程序的性能和响应速度
定义一个异步组件
Vue3 中要使用 defineAsyncComponent() 来定义异步组件,该 API 的入参是一个返回组件选项的函数,需要使用 **() => import()**函数来导入组件
defineAsyncComponent 方法接收一个返回 promise 的加载函数,这个 Promise 的 resolve 回调方法应该在从服务器获得组件定义时调用,我们也可以调用 reject 来表示加载失败
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
// ...从服务器获取组件
resolve(/* 获取到的组件 */);
});
});
ES 模块动态导入也会返回一个 Promise,所以我们会将它和 defineAsyncComponent 搭配使用,类似 Vite 和 Webpack 这样的构建工具也支持此语法 (并且会将它们作为打包时的代码分割点),因此我们也可以用它来导入 Vue 单文件组件
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
);
上面代码得到的 AsyncComponent 是一个外层包装过的组件,仅在页面需要它渲染时才会调用加载内部实际组件的函数
异步组件的加载与错误状态
我们在进行异步操作时,不可避免地会涉及到网络加载慢和加载错误的情况,vue 在设计 defineAsyncComponent() 组件时也考虑到了这种情况,它为我们提供了两个配置项即:加载异步组件时使用的组件 loadingComponent 和加载失败后展示的组件 errorComponent,我们需要创建两个自定义组件 LoadingComponent.vue 和 ErrorComponent.vue 组件,作为上面两个配置项使用示例代码如下
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./MyComponent.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000,
});
上述代码在页面加载时,先加载 LoadingComponent 组件,在加载组件显示之前有一个默认的 200ms 延迟,如果加载失败,则会调用 ErrorComponent 组件,还可以指定一个超时时间 timeout,在请求耗时超过指定时间时也会渲染报错组件
搭配 Suspense 使用
什么是 Suspense
Suspense 是一个内置的组件,可以将子组件的加载状态统一管理,包括异步组件的加载状态,它可以在异步加载组件的时候有一个 loading 状态,等异步组件创建好之后,再显示组件
Suspense 组件有两个插槽: #default 和 #fallback,两个插槽都只允许一个直接子节点
在初始渲染时,Suspense 将在内存中渲染其默认的#default 插槽内容,如果在这个过程中遇到任何异步依赖,则会进入挂起状态,在挂起状态期间,展示的是#fallback 后备内容,当所有遇到的异步依赖都完成后,Suspense 会进入完成状态,并将展示出默认插槽#default 的内容
如果在初次渲染时没有遇到异步依赖,Suspense 会直接进入完成状态
进入完成状态后,只有当默认插槽#default 的根节点被替换时,Suspense 才会回到挂起状态,组件树中新的更深层次的异步依赖不会造成 Suspense 回退到挂起状态
发生回退时, #fallback 后备内容不会立即展示出来,相反,Suspense 在等待新内容和异步依赖完成时,会展示之前 #default 插槽的内容,这个行为可以通过一个 timeout prop 进行配置:在等待渲染新内容耗时超过 timeout 之后,Suspense 将会切换为展示 #fallback 后备内容,若 timeout 值为 0 将导致在替换#default 默认内容时立即显示 #fallback 后备内容
Suspense 事件
Suspense 组件会触发三个事件: pending、resolve 和 fallback,pending 事件是在进入挂起状态时触发,resolve 事件是在 default 插槽完成获取新内容时触发,fallback 事件则是在 fallback 插槽的内容显示时触发
异步组件的 suspensible 特性
异步组件默认就是“suspensible”的,这意味着如果组件关系链上有一个 Suspense,那么这个异步组件就会被当作这个 Suspense 的一个异步依赖,在这种情况下,加载状态是由 Suspense 控制,而该组件自己的加载、报错、延时和超时等选项都将被忽略
异步组件也可以通过在选项中指定 suspensible: false 表明不用 Suspense 控制,并让组件始终自己控制其加载状态
异步组件和 Suspense 配合使用的示例代码
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<p>loading...</p>
</template>
</Suspense>
</template>
一个小示例
index.vue
<script setup>
import { defineAsyncComponent } from 'vue';
import Loading from './Loading.vue';
const asyncComponent = defineAsyncComponent({
// 加载函数
loader: () => import('./Home.vue'),
// 加载异步组件时,使用的组件
loadingComponent: Loading,
// 展示加载组件前的延迟时间,默认为200ms
delay: 200,
});
</script>
<template>
<Suspense>
<template #fallback>
<Loading />
</template>
<template #default>
<asyncComponent />
</template>
</Suspense>
</template>
<style scoped></style>
Laoding.vue
<script setup></script>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
style="
margin: auto;
background: #fff;
display: block;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
"
width="100px"
height="100px"
viewBox="0 0 100 100"
preserveAspectRatio="xMidYMid"
>
<defs>
<clipPath id="ldio-zcsoed0lnoi-cp" x="0" y="0" width="100" height="100">
<rect x="0" y="5" width="100" height="46"></rect>
</clipPath>
</defs>
<path
d="M70 75.2H34.1l-4.1-18.4l-0.7-3l-1-4.7c0 0 0 0 0-0.1c0-0.1 0-0.1-0.1-0.2c0 0 0-0.1-0.1-0.1c0 0 0-0.1-0.1-0.1 c0 0-0.1-0.1-0.1-0.1c0 0-0.1-0.1-0.1-0.1c0 0-0.1-0.1-0.1-0.1c0 0 0 0-0.1-0.1L22.3 44c0-0.1 0-0.2 0-0.3c0-1.9-1.6-3.5-3.5-3.5 s-3.5 1.6-3.5 3.5c0 1.9 1.6 3.5 3.5 3.5c0.7 0 1.4-0.2 2-0.6l4.8 3.7L31.5 77c0 0 0 0 0 0l-5.6 7.7c-0.3 0.5-0.4 1.1-0.1 1.6 c0.3 0.5 0.8 0.8 1.3 0.8h4c-0.8 0.8-1.3 1.9-1.3 3.2c0 2.6 2.1 4.7 4.7 4.7c2.6 0 4.7-2.1 4.7-4.7c0-1.2-0.5-2.3-1.3-3.2h29 c-0.8 0.8-1.3 1.9-1.3 3.2c0 2.6 2.1 4.7 4.7 4.7c2.6 0 4.7-2.1 4.7-4.7c0-1.2-0.5-2.3-1.3-3.2H77c0.8 0 1.5-0.7 1.5-1.5 s-0.7-1.5-1.5-1.5H30l4.3-6h36.8c0.7 0 1.3-0.5 1.4-1.1l7.5-27.3c0.2-0.8-0.2-1.6-1-1.8c-0.8-0.2-1.6 0.2-1.8 1l-1.3 4.7l-0.8 3"
fill="#dddddd"
></path>
<polygon
points="31.3 53.1 35.7 73.2 68.5 73.2 74 53.1"
fill="#dddddd"
></polygon>
<g clip-path="url(#ldio-zcsoed0lnoi-cp)">
<g>
<g transform="translate(50 41)">
<path
d="M6.5-6.7C6.1-6.9 5.7-7.2 5.3-7.4C5-7.5 4.6-7.7 4.3-7.8C3.1-2.2-4-3.7-2.9-9.3c-0.4 0-0.7 0-1.1 0 c-0.5 0-1 0.1-1.4 0.2c-1.8 0.3-3.6 0.9-5.3 1.8l1.1 4.2l3.1-0.8L-8.7 6.9L3.2 9.3L5.4-1.5l2.5 2l2.7-3.4C9.5-4.4 8.1-5.7 6.5-6.7z"
fill="#e15b64"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(35 17)">
<path
d="M3.4-5.3L2.5-5l0.8-2.3L1.1-6.3l-1.2-2.2l-1.6 4.6l-4.6-1.6l0.9 2.3l-2.2 1.2l2.3 0.8L-6-0.9 c-0.6 0.3-0.8 0.9-0.5 1.5l1 2.1C-5.2 3.4-4.6 3.6-4 3.3l0.1-0.1l2.1 4.5C-1.4 8.4-0.7 8.7 0 8.3l1.7-0.8l1.7-0.8L5 5.9l1.7-0.8 C7.4 4.8 7.7 4 7.4 3.3L5.2-1.1l0.1-0.1c0.6-0.3 0.8-0.9 0.5-1.5l-1-2.1C4.6-5.4 3.9-5.6 3.4-5.3z"
fill="#f47e60"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(66 26)">
<path
d="M-4.5-3.7L1.9-6l0.5-0.2L2-7.2l-6.9 2.5C-5.7-4.4-6.1-3.5-6-2.7c0 0.1 0 0.2 0.1 0.3l3 8.2 C-2.5 6.9-1.3 7.4-0.2 7l5.6-2C5.9 4.8 6.2 4.2 6 3.7L3.2-3.9l-0.4-1L2.4-4.7L1.9-4.5l-3.2 1.2l-2.7 1c-0.3 0.1-0.6 0-0.8-0.2 c-0.1-0.1-0.1-0.1-0.1-0.2C-5.1-3.1-4.9-3.6-4.5-3.7z"
fill="#f8b26a"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(55 6)">
<polygon
points="0 -4.9 1.6 -1.7 5.1 -1.1 2.6 1.3 3.2 4.9 0 3.2 -3.2 4.9 -2.6 1.3 -5.1 -1.1 -1.6 -1.7"
fill="#abbd81"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</polygon>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
</g>
<g clip-path="url(#ldio-zcsoed0lnoi-cp)">
<g transform="translate(0 -75)">
<g>
<g transform="translate(50 41)">
<path
d="M6.5-6.7C6.1-6.9 5.7-7.2 5.3-7.4C5-7.5 4.6-7.7 4.3-7.8C3.1-2.2-4-3.7-2.9-9.3c-0.4 0-0.7 0-1.1 0 c-0.5 0-1 0.1-1.4 0.2c-1.8 0.3-3.6 0.9-5.3 1.8l1.1 4.2l3.1-0.8L-8.7 6.9L3.2 9.3L5.4-1.5l2.5 2l2.7-3.4C9.5-4.4 8.1-5.7 6.5-6.7z"
fill="#e15b64"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(35 17)">
<path
d="M3.4-5.3L2.5-5l0.8-2.3L1.1-6.3l-1.2-2.2l-1.6 4.6l-4.6-1.6l0.9 2.3l-2.2 1.2l2.3 0.8L-6-0.9 c-0.6 0.3-0.8 0.9-0.5 1.5l1 2.1C-5.2 3.4-4.6 3.6-4 3.3l0.1-0.1l2.1 4.5C-1.4 8.4-0.7 8.7 0 8.3l1.7-0.8l1.7-0.8L5 5.9l1.7-0.8 C7.4 4.8 7.7 4 7.4 3.3L5.2-1.1l0.1-0.1c0.6-0.3 0.8-0.9 0.5-1.5l-1-2.1C4.6-5.4 3.9-5.6 3.4-5.3z"
fill="#f47e60"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(66 26)">
<path
d="M-4.5-3.7L1.9-6l0.5-0.2L2-7.2l-6.9 2.5C-5.7-4.4-6.1-3.5-6-2.7c0 0.1 0 0.2 0.1 0.3l3 8.2 C-2.5 6.9-1.3 7.4-0.2 7l5.6-2C5.9 4.8 6.2 4.2 6 3.7L3.2-3.9l-0.4-1L2.4-4.7L1.9-4.5l-3.2 1.2l-2.7 1c-0.3 0.1-0.6 0-0.8-0.2 c-0.1-0.1-0.1-0.1-0.1-0.2C-5.1-3.1-4.9-3.6-4.5-3.7z"
fill="#f8b26a"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(55 6)">
<polygon
points="0 -4.9 1.6 -1.7 5.1 -1.1 2.6 1.3 3.2 4.9 0 3.2 -3.2 4.9 -2.6 1.3 -5.1 -1.1 -1.6 -1.7"
fill="#abbd81"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</polygon>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
</g>
</g>
</svg>
</template>
<style scoped></style>
Home.vue
<script setup>
import { getHomePage } from '@/service/modules/home';
import { ref } from 'vue';
let result = await getHomePage();
const hero = ref(result.hero);
</script>
<template>
<ul>
<li v-for="item in hero" :key="item">{{ item.category }}</li>
</ul>
</template>
<style scoped></style>