Published on

Vue - 异步组件

Authors
  • avatar
    Name
    Deng Hua
    Twitter

异步组件是 Vue.js 中以异步方式加载的特殊组件,它们仅在需要时从服务器获取,并不包含在初始页面加载中。这种方法可以通过减少不必要的网络请求来降低网络开销,缩短页面加载时间,从而增强用户体验。

目录

哪些组件适合异步加载?

  • 大型或复杂的组件,例如页面底部的slider,不是第一时间要渲染的UI等。

  • 基于用户交互(例如弹窗和Modal窗口)呈现的组件。

  • 用于访问频率较低或用户较少的功能的组件。

Vue.js 异步组件的实际使用

创建一个简单的删除确认Modal

<!-- AppModal.vue -->
<template>
  <div class="backdrop">
    <div class="modal">
      <div v-if="slots.header" class="header">
        <slot name="header" />
      </div>
      <div v-if="slots.body" class="body">
        <slot name="body" />
      </div>
      <div v-if="slots.footer" class="footer">
        <slot name="footer" />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { useSlots } from "vue";
  const slots = useSlots();
</script>

<style scoped>
  .backdrop {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    background: rgba(0, 0, 0, 0.03);
    z-index: 10;
  }
  .modal {
    width: 100%;
    width: 320px;
    background: #fff;
    border: 1px solid #e6e6e6;
    border-radius: 16px;
  }
  .header {
    width: 100%;
    padding: 16px 24px;
    border-bottom: 1px solid #e6e6e6;
    background: #fafafa;
    border-top-left-radius: 16px;
    border-top-right-radius: 16px;
  }
  .body {
    width: 100%;
    padding: 16px 24px;
  }
  .footer {
    width: 100%;
    padding: 16px 24px;
  }
</style>

删除确认Modal:

<!-- DeleteConfirmationModal.vue -->
<template>
  <AppModal>
    <template #header>
      <p class="heading">删除确认</p>
    </template>
    <template #body>
      您确定要删除此内容吗?这将无法撤销。
    </template>

    <template #footer>
      <div class="controls">
        <button @click="handleCancel" class="cancel">取消</button>
        <button @click="handleDelete" class="delete">删除</button>
      </div>
    </template>
  </AppModal>
</template>

<script setup lang="ts">
  import AppModal from "./AppModal.vue";

  const emit = defineEmits<{
    cancel: [];
    delete: [];
  }>();

  function handleCancel() {
    emit("cancel");
  }
  function handleDelete() {
    emit("delete");
  }
</script>

<style scoped>
  .heading {
    font-weight: 600;
  }
  .controls {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
  }
  .cancel,
  .delete {
    border: none;
    background: none;
    cursor: pointer;
    padding: 12px 24px;
    border-radius: 12px;
    font-weight: 700;
  }

  .delete {
    background: red;
    color: #fff;
  }
</style>

最后在App.vue内使用

<template>
  <DeleteConfirmationModal v-if="deleteModalOpen" @cancel="handleCancel" @delete="handleDeleteConfirmation" />

  <div class="resources">
    <div v-if="resourceExists" class="resource">
      <p>一段内容</p>
      <button class="delete" @click="handleDelete">删除</button>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { ref } from "vue";
  import DeleteConfirmationModal from "./DeleteConfirmationModal.vue";

  const deleteModalOpen = ref(false);
  const resourceExists = ref(true);

  function handleDelete() {
    deleteModalOpen.value = true;
  }
  function handleCancel() {
    deleteModalOpen.value = false;
  }
  function handleDeleteConfirmation() {
    resourceExists.value = false;
    deleteModalOpen.value = false;
  }
</script>

<style scoped>
  .delete {
    border: none;
    background: none;
    cursor: pointer;
    background: red;
    color: #fff;
    padding: 8px 12px;
    border-radius: 10px;
    font-weight: 700;
  }
  .resources {
    width: 100%;
    height: 100dvh;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .resource {
    display: flex;
    gap: 16px;
    align-items: center;
    font-weight: 500;
  }
</style>

最终效果:


我们跳转到 DevTools 中的 network 选项卡,可以注意到我们的Modal(<AppModal><DeleteConfirmationModal>)是在应用初次加载时下载的,即便它并不需要首次加载。

当代码和应用功能变得庞大且复杂时,这可能会导致用户等待更长时间才能看到所需的内容。

异步组件可以快捷的解决这个问题。通过 DefineAsyncComponent 函数来利用它们,Vue为我们提供了开箱即用的功能:

import { defineAsyncComponent } from "vue";

const DeleteConfirmationModal = defineAsyncComponent(
  () => import("./DeleteConfirmationModal.vue")
);

DefineAsyncComponent函数接受一个返回 Promise 的loader函数,这是 ES Module动态导入的模块。我们可以利用它在我们的应用中实现异步加载:

在导入DeleteConfirmationModal组件的地方改写逻辑:

//  import DeleteConfirmationModal from "./DeleteConfirmationModal.vue";
import { ref, defineAsyncComponent, Component } from "vue";

const DeleteConfirmationModal = defineAsyncComponent(
  (): Promise<Component> => import('./DeleteConfirmationModal.vue')
)
// ...

更新上述代码后,我们可以在 DevTools 中注意到,在初始页面加载期间不再下载ModalModal组件以及所有的依赖项会在用户单击“删除”按钮后下载。

这种方法可以实现更快的初始页面加载速度,从而提高应用程序的性能,特别是首屏性能,并且还可以节省不必要的网络请求。

以这个例子为例,实际业务中,可能一定比例的用户永远都不会用到删除功能。

End.