• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • Suspense 异步依赖

    实验性功能:<Suspense>是一项实验性功能。不能保证它会成为稳定版,而且在那之前,相关 API 也可能会发生变化。

    <Suspense>是一个内置组件,用来在组件树中编排异步依赖。它可以在等待组件树下的多个嵌套异步依赖项解析完成时,呈现加载状态。

    异步依赖

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

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

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

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

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

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


    async setup()

    组合式 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>
    
    <template>
      {{ posts }}
    </template>
    


    异步组件

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

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


    加载中状态

    <Suspense>组件有两个插槽:#default#fallback。两个插槽都只允许一个直接子节点。在可能的时候都将显示,#default默认槽中的节点。否则将显示后备槽中的节点。

    <Suspense>
      <!-- 具有深层异步依赖的组件 -->
      <Dashboard />
    
      <!-- 在 #fallback 插槽中显示 “正在加载中” -->
      <template #fallback>
        Loading...
      </template>
    </Suspense>
    

    在初始渲染时,<Suspense>将在内存中,渲染其#default默认的插槽内容。如果在这个过程中遇到任何异步依赖,则会进入挂起状态。在挂起状态期间,展示的是后备内容。当所有遇到的异步依赖都完成后,<Suspense>会进入完成状态,并将展示出#default默认插槽的内容。

    如果在初次渲染时没有遇到异步依赖,<Suspense>会直接进入完成状态。

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

    发生回退时,后备内容不会立即展示出来。相反,<Suspense>在等待新内容和异步依赖完成时,会展示之前#default插槽的内容。这个行为可以通过一个timeout prop 进行配置:在等待渲染新内容耗时超过 timeout 之后,<Suspense>将会切换为展示后备内容。若timeout值为0将导致在替换默认内容时立即显示后备内容。


    事件

    除了pending事件之外,<Suspense>组件还有pendingresolvefallback事件。

    • pending事件是在进入挂起状态时触发。
    • resolve事件是在default插槽完成获取新内容时触发。
    • fallback事件则是在fallback插槽展示时触发。

    例如,可以使用这些事件在加载新组件时在之前的 DOM 最上层显示一个加载指示器。


    错误处理

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


    和其他组件结合

    我们常常会将<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>
    

    Vue Router 使用动态导入对懒加载组件进行了内置支持。这些与异步组件不同,目前他们不会触发<Suspense>。但是,它们仍然可以有异步组件作为后代,这些组件可以照常触发<Suspense>