vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > vue3内置组件<Suspense>

vue3中的内置组件<Suspense>用法

作者:fishmemory7sec

<Suspense>是 Vue3 中的一个实验性功能,用于处理异步组件和依赖,它允许你在等待多个异步依赖项解析时显示一个加载状态,并提供了超时处理、错误捕获和嵌套使用等功能,通过合理使用<Suspense>,可以提升用户体验,特别是在处理复杂异步操作时

官方文档:

官方提示:

异步依赖

要了解 <Suspense> 所解决的问题和它是如何与异步依赖进行交互的,需要了解什么是异步依赖。

<Suspense>主要用于包裹异步组件,这些组件可能需要进行异步数据获取或其他耗时的操作:

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

在这个组件树中有多个嵌套组件,要渲染出它们,首先得解析一些异步资源。

如果没有 <Suspense>组件:

有了 <Suspense> 组件后:

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

async setup()

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

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

<script setup>语法糖

<script setup>语法糖中,开发者无法在前面添加async

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

<!-- Child.vue -->
<template>
  <div class="child-box">
    <h1>子组件</h1>
    <h2>网易热歌榜</h2>
  </div>
</template>
<script setup lang="ts">
// 获取网易云热歌榜
let res = await fetch('https://api.uomg.com/api/rand.music?sort=热歌榜&format=json')
console.log(res.json()) // 一个Promise
</script>

在父组件中直接引用Child.vue组件:

<template>
  <div class="home-wrap">
    <h1>主页----父组件</h1>
    <Child />
  </div>
</template>
<script setup lang="ts">
import Child from './Child.vue';
</script>

页面没有显示Child.vue的内容,并且在控制台提示:

[Vue warn]: Component : setup function returned a promise, but no boundary was found in the parent component tree. A component with async setup() must be nested in a in order to be rendered.

setup函数返回了一个promise,但是在父组件树中没有找到<Suspense> 边界。带有async setup()的组件必须嵌套在<Suspense> 中才能呈现。

按照提示修改父组件:

<template>
  <div class="home-wrap">
    <h1>主页----父组件</h1>
    <Suspense>
      <!-- 异步任务ok后 -->
      <template v-slot:default>
        <Child />
      </template>
      <!-- 异步任务未做完时 -->
      <template v-slot:fallback>
        <div>请求中...</div>
      </template>
    </Suspense>
  </div>
</template>
<script setup lang="ts">
import { Suspense } from 'vue';
import Child from './Child.vue';
</script>

页面成功展示Child.vue的内容:

异步组件

异步组件默认就是suspensible: true的。这意味着如果组件关系链上有一个 <Suspense>,那么这个异步组件就会被当作这个 <Suspense> 的一个异步依赖。在这种情况下,加载状态是由 <Suspense> 控制,而该组件自己的加载、报错、延时和超时等选项都将被忽略。

异步组件也可以通过在选项中指定 suspensible: false 表明不用 Suspense 控制,并让组件始终自己控制其加载状态。

<Suspense>

props

interface SuspenseProps {
  timeout?: string | number
  suspensible?: boolean
}

参数说明:

timeout:可选参数。

suspensible:可选参数。

插槽

<Suspense> 组件接受两个插槽:#default#fallback。两个插槽都只允许一个直接子节点。

#default 插槽用于放置异步依赖的组件或内容。

如果在渲染时遇到异步组件或具有 async setup() 的组件,<Suspense> 会等待所有异步依赖项解析完成后再显示 #default 插槽的内容。

#fallback:在异步依赖解析完成之前,<Suspense> 会显示 #fallback 插槽中的内容。

<Suspense>
  <!-- 具有深层异步依赖的组件 -->
  <template #fallback>
    <Dashboard />
  </template>
  

  <!-- 在 #fallback 插槽中显示 “正在加载中” -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

初始渲染

挂起状态

完成状态

状态切换条件

官方说明:进入完成状态后,只有当默认插槽的根节点被替换时,<Suspense> 才会回到挂起状态。组件树中新的更深层次的异步依赖不会造成 <Suspense> 回退到挂起状态。

一旦进入完成状态,只有当 #default 插槽的根节点被替换时,<Suspense>才会回到挂起状态。

通过设置 timeout 属性,配置过度行为:

如果只是在<Dashboard />组件的子组件树中添加了新的异步依赖,<Suspense>不会回退到挂起状态。

事件

<Suspense> 组件会触发三个事件:pendingresolvefallback

@pending 是在进入挂起状态时触发。

@resolve 是在 default 插槽完成获取新内容时触发。

@fallback 是在 fallback 插槽的内容显示时触发。

错误处理

<Suspense> 组件自身目前还不提供错误处理,可以使用 errorCaptured 选项或者 onErrorCaptured() 钩子,在使用到 <Suspense> 的父组件中捕获和处理异步错误。

errorCaptured选项

在 Vue 组件的选项中,可以定义errorCaptured方法。这个方法会在捕获到来自子孙组件(包括异步组件)的错误时被调用。

export default {
  errorCaptured(error, component, info) {
    // 处理错误的逻辑
    console.log('Error captured:', error);
    // 可以根据需要返回 false 来阻止错误继续向上传播
    return false;
  }
}

参数说明:

error参数是捕获到的错误对象,component是抛出错误的组件实例,info是一个包含错误来源信息的字符串。

当在包含<Suspense>的父组件中定义了errorCaptured选项时,如果<Suspense>内部的异步组件或异步操作抛出错误,errorCaptured方法将被调用。

<template>
  <div>
    <Suspense>
      <template v-slot:default>
        <Child />
      </template>

      <template v-slot:fallback>
        <div>Loading...</div>
      </template>
    </Suspense>
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  errorCaptured(error, component, info) {
    console.log('Error in Suspense:', error);
  }
});
</script>
<script setup lang="ts">
import { Suspense } from 'vue';
import Child from './Child.vue';
</script>

onErrorCaptured ()钩子(组合式 API)

在 Vue 3 的组合式 API 中,可以使用onErrorCaptured函数来注册一个错误捕获钩子。

<script setup>
import { onErrorCaptured } from 'vue';
onErrorCaptured((error, component, info) => {
  console.log('Error captured with onErrorCaptured:', error);
});
</script>

当在包含<Suspense>的父组件中使用onErrorCaptured钩子时,如果<Suspense>内部的异步组件或异步操作抛出错误,这个钩子将被调用。

注意

errorCaptured方法或onErrorCaptured钩子中,可以根据需要返回false来阻止错误继续向上传播。

如果不返回false或者返回true,错误将继续向上传递给父组件的错误处理机制。

嵌套使用

当有多个类似于下方的异步组件 (常见于嵌套或基于布局的路由) 时:

<Suspense>
  <component :is="DynamicAsyncOuter">
    <component :is="DynamicAsyncInner" />
  </component>
</Suspense>

<Suspense> 创建了一个边界,它将如预期的那样解析树下的所有异步组件。

可以使用嵌套的方法来解决这个问题:

<Suspense>
  <component :is="DynamicAsyncOuter">
    <Suspense suspensible> <!-- 像这样 -->
      <component :is="DynamicAsyncInner" />
    </Suspense>
  </component>
</Suspense>

设置 suspensible 属性后,所有异步依赖项处理都会交给父级 <Suspense> (包括发出的事件),而内部 <Suspense> 仅充当依赖项解析和修补的另一个边界。

如果不设置 suspensible 属性,内部的 <Suspense> 将被父级 <Suspense> 视为同步组件。

这意味着它将会有自己的回退插槽,如果两个 Dynamic 组件同时被修改,则当子 <Suspense> 加载其自己的依赖关系树时,可能会出现空节点和多个修补周期。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文