Skip to content

首先需要理解一下什么是异步组件

在 vue 中,当我们注册全局组件或者局部组件时,他们都是同步的被立即解析并加载的,这意味着在程序初始化时,所有组件都会通过网络被下载到内存中,并且在内存中占用一定的资源,预加载所有组件的话,会将页面的初始化加载时间和性能降低,尤其是在移动端上,为了避免这种情况,vue 才提供了异步组件

为什么要用异步组件?他有什么好处?

  1. 异步组件可以将我们的组件分开打包,按需加载,这样可以减轻初始化页面时的加载时间和减少资源浪费
  2. 当组件需要和路由配合使用时,异步组件也能帮助我们实现按需加载和动态导入,这样可以在调用组件时再切换路由实现动态加载组件,有助于提高应用程序的性能和响应速度

定义一个异步组件

Vue3 中要使用 defineAsyncComponent() 来定义异步组件,该 API 的入参是一个返回组件选项的函数,需要使用 **() => import()**函数来导入组件

defineAsyncComponent 方法接收一个返回 promise 的加载函数,这个 Promise 的 resolve 回调方法应该在从服务器获得组件定义时调用,我们也可以调用 reject 来表示加载失败

js
import { defineAsyncComponent } from 'vue';

const AsyncComponent = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...从服务器获取组件
    resolve(/* 获取到的组件 */);
  });
});

ES 模块动态导入也会返回一个 Promise,所以我们会将它和 defineAsyncComponent 搭配使用,类似 Vite 和 Webpack 这样的构建工具也支持此语法 (并且会将它们作为打包时的代码分割点),因此我们也可以用它来导入 Vue 单文件组件

js
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
);

上面代码得到的 AsyncComponent 是一个外层包装过的组件,仅在页面需要它渲染时才会调用加载内部实际组件的函数

异步组件的加载与错误状态

我们在进行异步操作时,不可避免地会涉及到网络加载慢和加载错误的情况,vue 在设计 defineAsyncComponent() 组件时也考虑到了这种情况,它为我们提供了两个配置项即:加载异步组件时使用的组件 loadingComponent 和加载失败后展示的组件 errorComponent,我们需要创建两个自定义组件 LoadingComponent.vue 和 ErrorComponent.vue 组件,作为上面两个配置项使用示例代码如下

js
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 配合使用的示例代码

vue
<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <p>loading...</p>
    </template>
  </Suspense>
</template>

一个小示例

index.vue

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

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

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>

效果

上次更新于: