在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。这样做的目的可以提高页面加载速度,优化用户体验,减少首屏白屏时间

defineAsyncComponent 方法接收一个返回 Promise 的加载函数。这个 Promise 的 resolve 回调方法应该在从服务器获得组件定义时调用。你也可以调用 reject(reason) 表明加载失败。

defineAsyncComponent单独使用

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

异步组件简单用法:只有在使用的时候才会加载

<template>
  <div class="content">
      <asyncCom></asyncCom>
  </div>
</template>

<script setup lang="ts">
import { defineAsyncComponent, markRaw, reactive, ref, Ref } from 'vue';
let asyncCom = defineAsyncComponent(() => import('./asyncCom.vue'))

</script>

复杂用用法:

const AsyncComp = defineAsyncComponent({
  // 加载函数
  loader: () => import('./Foo.vue'),

  // 加载异步组件时使用的组件
  loadingComponent: LoadingComponent,
  // 展示加载组件前的延迟时间,默认为 200ms
  delay: 200,

  // 加载失败后展示的组件
  errorComponent: ErrorComponent,
  // 如果提供了一个 timeout 时间限制,并超时了
  // 也会显示这里配置的报错组件,默认值是:Infinity
  timeout: 3000
})

传递对象类型作为参数
defineAsyncComponent方法也可以接收一个对象作为参数,该对象中有如下几个参数:

loader:同工厂函数;
loadingComponent:加载异步组件时展示的组件;
errorComponent:加载组件失败时展示的组件;
delay:显示loadingComponent之前的延迟时间,单位毫秒,默认200毫秒;
timeout:如果提供了timeout,并且加载组件的时间超过了设定值,将显示错误组件,默认值为Infinity(单位毫秒);
suspensible:异步组件可以退出<Suspense>控制,并始终控制自己的加载状态。具体可以参考文档;
onError:一个函数,该函数包含4个参数,分别是error、retry、fail和attempts,这4个参数分别是错误对象、重新加载的函数、加载程序结束的函数、已经重试的次数。

 

复杂用法举例:正常加载的话会很快,我们看不到效果,所以做了延时加载

<template>
  <div class="content">
    <div class="async-component">
      <button @click="isShow = true">clickccc</button>
      <asyncCom v-if="isShow"></asyncCom>
    </div>
  </div>
</template>
<script setup lang="ts">
import { defineAsyncComponent, markRaw, reactive, ref, Ref } from 'vue';
import cpA from './cpA.vue';
import cpB from './cpB.vue';
import cpC from './cpC.vue';
let num = 0
let asyncCom = defineAsyncComponent({
  loader: () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let res: any = import('./asyncCom.vue')
        num++
        console.log("numsssss", num)
        if (num < 3) {
          reject(res)
        } else {
          resolve(res)
        }
      }, 300)

    })
  },
  loadingComponent: cpB,
  delay: 100,
  errorComponent: cpC,
  timeout: 5000,
  suspensible: true,
  onError(error, retry, fail, attempts) {
   
    // 注意,retry/fail 就像 promise 的 resolve/reject 一样:
    // 必须调用其中一个才能继续错误处理。
    if (attempts < 3) {
      // 请求发生错误时重试,最多可尝试 3 次
     console.log("qingqiu ", attempts)
      retry()
    } else {
      fail()
    }
  }
})

let isShow: Ref<Boolean> = ref(false)

</script>

上面代码做了前两次会请求错误,第三次会请求成功,下面是效果:

 

修改代码attempts判断2次就抛出错误,然后加载errorComponent组件 , 模拟组件加载错误显示组件

let num = 0
let asyncCom = defineAsyncComponent({
  loader: () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let res: any = import('./asyncCom.vue')
        num++
        console.log("numsssss", num)
        if (num < 3) {
          reject(res)
        } else {
          resolve(res)
        }
      }, 300)

    })
  },
  loadingComponent: cpB,
  delay: 100,
  errorComponent: cpC,
  timeout: 5000,
  suspensible: true,
  onError(error, retry, fail, attempts) {
   
    // 注意,retry/fail 就像 promise 的 resolve/reject 一样:
    // 必须调用其中一个才能继续错误处理。
    if (attempts < 2) {
      // 请求发生错误时重试,最多可尝试 3 次
     console.log("qingqiu ", attempts)
      retry()
    } else {
      fail()
    }
  }
})

2次请求错误后抛出错误,显示错误组件效果如下:

 

以上是defineAsyncComponent单独使用;

defineAsyncComponent 搭配 Suspense 使用

异步依赖#

要了解 <Suspense> 所解决的问题和它是如何与异步依赖进行交互的,我们需要想象这样一种组件层级结构:

<Suspense>
└─ <Dashboard>
   ├─ <Profile>
   │  └─ <FriendStatus>(组件有异步的 setup())
   └─ <Content>
      ├─ <ActivityFeed> (异步组件)
      └─ <Stats>(异步组件)

在这个组件树中有多个嵌套组件,要渲染出它们,首先得解析一些异步资源。如果没有 <Suspense>,则它们每个都需要处理自己的加载、报错和完成状态。在最坏的情况下,我们可能会在页面上看到三个旋转的加载态,在不同的时间显示出内容。

有了 <Suspense> 组件后,我们就可以在等待整个多层级组件树中的各个异步依赖获取结果时,在顶层展示出加载中或加载失败的状态。

<Suspense> 可以等待的异步依赖有两种:

  1. 带有异步 setup() 钩子的组件。这也包含了使用 <script setup> 时有顶层 await 表达式的组件。

  2. 异步组件

组合式 API 中组件的 setup() 钩子可以是异步的

export default {
  async setup() {
    const res = await fetch(...)
    const posts = await res.json()
    return {
      posts
    }
  }
}

<script setup>,那么顶层 await 表达式会自动让该组件成为一个异步依赖:

<script setup>
const res = await fetch(...)
const posts = await res.json()
</script>

 

第一步:改写异步组件 asyncCom.vue

<template>
  <div>
    我是异步组件{{resdata}}
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
let resdata = ref<any>('')


let testFun = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("1313")
      resolve("SUCCESS")
    }, 1000)
  })
}
resdata = await testFun()

</script>

<style scoped>
</style>

第二步:父组件引用

<template>
  <div class="content">
    <div class="async-component">
      <button @click="isShow = true">clickccc</button>

      <Suspense v-if="isShow">
        <template #default>
          <asyncCom />
        </template>
        <template #fallback>
          <p> Loading... </p>
        </template>
      </Suspense>

    </div>
   
  </div>
</template>

<script setup lang="ts">
import { defineAsyncComponent, markRaw, reactive, ref, Ref } from 'vue';


let asyncCom = defineAsyncComponent(()=>import('./asyncCom.vue'))

let isShow: Ref<Boolean> = ref(false)


</script>

<suspense> 组件有两个插槽。它们都只接收一个直接子节点。default 插槽里的节点会尽可能展示出来。如果不能,则展示 fallback 插槽里的节点。上面代码显示:首先加载组件 asyncCom,由于组件异步加载数据,所以此时会显示#fallback中内容,等待组件asyncCom加载完成功后显示asyncCom,页面效果如下:

 

和其他组件结合,嵌套顺序

我们常常会将 <Suspense> 和 <Transition><KeepAlive> 等组件结合。要保证这些组件都能正常工作,嵌套的顺序非常重要。

另外,这些组件都通常与 Vue Router 中的 <RouterView> 组件结合使用。

下面的示例展示了如何嵌套这些组件,使它们都能按照预期的方式运行。若想组合得更简单,你也可以删除一些你不需要的组件:

<RouterView v-slot="{ Component }">
  <template v-if="Component">
    <Transition mode="out-in">
      <KeepAlive>
        <Suspense>
          <!-- 主要内容 -->
          <component :is="Component"></component>

          <!-- 加载中状态 -->
          <template #fallback>
            正在加载...
          </template>
        </Suspense>
      </KeepAlive>
    </Transition>
  </template>
</RouterView>

Logo

前往低代码交流专区

更多推荐