找回密码
 立即注册

Vue3 Suspense:是什么?能干什么?如何用

2022-7-29 22:23:33 · 站长社区

本篇文章带大家深入了解一下Vue3 Suspense,聊聊Suspense是什么、能干什么,以及如何使用它,希望对大家有所帮助!


222330oy2omop7nlvzo27o.jpg

Suspense 不是你想的那样。是的,它帮助我们处理异步组件,但它的作用远不止于此。(学习视频分享:vue视频教程

Suspense 允许我们协调整个应用程序的加载状态,包括所有深度嵌套的组件。而不是像一个爆米花用户界面一样,到处都是 loading,组件突然奔的一下到位

222330dri3ciijh9g3y3zb.gif

有了 Suspense, 我们可以有一个单一的、有组织的系统,一次性加载所有东西。

222330u9a0qp75tf7fnafq.gif

而且,Suspense 也给我们提供了细粒度的控制,所以如果需要的话,我们可以在两者之间实现一些东西。

在这篇文章中,我们将学到很多关于 Suspense 的知识--它是什么,能干什么,以及如何使用它。

首先,我们将仔细看看这些爆米花界面。然后,在看看如何使用Suspense来解决这些问题。之后,尝试通过在整个应用程序中嵌套Suspense来获得更精细的控制。最后,简单看看如何使用占位符来丰富我们的用户界面。

爆米花UI-- Suspense 之前

事例地址:https://codesandbox.io/s/uncoordinated-loading-before-suspense-srh8ll?file=/src/App.vue

如果没有 Suspense,每个组件都必须单独处理其加载状态。

222330jtoyvykozzaatsf2.gif

这可能导致一些相当糟糕的用户体验,多个加载按钮和内容突然出现在屏幕上,就像你在制作爆米花一样。

虽然,我们可以创建抽象组件来处理这些加载状态,但这比使用Suspense要困难得多。有一个单一的点来管理加载状态,比每个组件做自己的事情要容易维护得多。

在事例中,我们使用BeforeSuspense组件来模拟一个内部处理加载状态的组件。把它命名为BeforeSuspense,因为一旦我们实现了Suspense,我们就会把它重构为WithSuspense组件。

BeforeSuspense.vue

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

<template>

    <div class=async-component :class=!loading && loaded>

      <Spinner v-if=loading />

      <slot />

    </div>

</template>

 

<script setup>

import { ref } from vue

import Spinner from ./Spinner.vue

 

const loading = ref(true)

const { time } = defineProps({

  time: {

    type: Number,

    default: 2000

  }

})

 

setTimeout(() => (loading.value = false), time)

</script>

一开始设置 loading 为 true,所以显示 Spinner 组件。然后,当setTimeout完成后,将 loading 设置为 false,隐藏 Spinner 并使组件的背景为绿色。

在这个组件中,还包括一个 slot ,这样其它组件就可以套在 BeforeSuspense 组件中了:

1

2

3

4

5

6

7

8

9

10

<template>

    <button @click=reload>Reload page</button>

    <BeforeSuspense time=3000>

      <BeforeSuspense time=2000 />

      <BeforeSuspense time=1000>

        <BeforeSuspense time=500 />

        <BeforeSuspense time=4000 />

      </BeforeSuspense>

    </BeforeSuspense>

</template>

没有什么太花哨的东西。只是一些嵌套的组件,有不同的时间值传递给它们。

下面,我们来看看怎么通过使用 Suspense 组件来改进这个爆米花用户界面。

Suspense

事例地址:https://codesandbox.io/s/coordinated-loading-with-suspense-b6dcbi?file=/src/App.vue

现在,我们使用Suspense来处理这些乱七八糟的东西,并将其变成一个更好的用户体验。

不过,首先我们需要快速了解一下Suspense到底是什么

Suspense 基础知识

以下是 Suspense 部分的基本结构

1

2

3

4

5

6

7

<Suspense>

  <!-- Async component here -->

 

  <template #fallback>

      <!-- Sync loading state component here -->

  </template>

</Suspense>

为了使用Suspense,把异步组件放入 default 槽,把回退加载状态放入 fallback 槽。

异步组件是以下两种情况之一:

  • 一个带有async setup函数的组件,该组件返回一个Promise,或者在script setup中使用顶级await

  • 使用 defineAsyncComponent 异步加载的组件

无论哪种方式,我们最终都会得到一个开始未解决 的 Promise ,然后最终得到解决。

当该 Promise 未被解决时,Suspense 组件将显示 fallback 槽中的内容。然后,当Promise 被解决时,它会在 default 槽中显示该异步组件。

注意: 这里没有错误处理路基。起初我以为有,但这是对悬念的一个常见误解。如果想知道是什么导致了错误。可以使用onErrorCaptured钩子来捕捉错误,但这是一个独立于Suspense的功能。

现在我们对Suspense有了一些了解,让我们回到我们的演示应用程序。

管理异步依赖关系

为了让Suspense管理我们的加载状态,首先需要将BeforeSuspense组件转换成一个异步组件

我们将它命名为 WithSuspense,内容如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

<template>

  <div class=async-component loaded>

    <!-- 这里不需要一个 Spiner 了,因为加载是在根部处理的 -->

    <slot />

  </div>

</template>

 

<script setup>

const { time } = defineProps({

  time: {

    type: Number,

    required: true

  }

})

// 加入一个延迟,以模拟加载数据

await new Promise(resolve => {

  setTimeout(() => {

    resolve()

  }, time)

})

</script>

我们已经完全删除了加载状态的Spinner,因为这个组件不再有加载状态了。

因为这是一个异步组件,setup 函数直到它完成加载才会返回。该组件只有在 setup 函数完成后才会被加载。因此,与BeforeSuspense组件不同,WithSuspense组件内容在加载完毕之前不会被渲染。

这对任何异步组件来说都是如此,不管它是如何被使用的。在setup函数返回(如果是同步的)或解析(如果是异步的)之前,它不会渲染任何东西。

有了WithSuspense组件,我们仍然需要重构我们的App组件,以便在Suspense组件中使用这个组件。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<template>

  <button @click=reload>Reload page</button>

  <Suspense>

    <WithSuspense :time=2000>

      <WithSuspense :time=1500 />

      <WithSuspense :time=1200>

        <WithSuspense :time=1000 />

        <WithSuspense :time=5000 />

      </WithSuspense>

    </WithSuspense>

 

    <template #fallback>

      <Spinner />

    </template>

  </Suspense>

</template>

结构和之前一样,但这次是在 Suspense 组件的默认槽中。我们还加入了 fallback 槽,在加载时渲染我们的Spinner组件。

在演示中,你会看到它显示加载按钮,直到所有的组件都加载完毕。只有在那时,它才会显示现在完全加载的组件树。

异步瀑布

如果你仔细注意,你会注意到这些组件并不像你想象的那样是并联加载的。

总的加载时间不是基于最慢的组件(5秒)。相反,这个时间要长得多。这是因为Vue只有在父异步组件完全解析后才会开始加载子组件。

你可以通过把日志放到WithSuspense组件中来测试这一点。一个在安装开始跟踪安装,一个在我们调用解决之前。

最初使用BeforeSuspense组件的例子中,整个组件树被挂载,无需等待,所有的 异步 操作都是并行启动的。这意味着Suspense有可能通过引入这种异步瀑布而影响性能。所以请记住这一点。

嵌套 Suspense 以隔离子树

事例地址:https://codesandbox.io/s/nesting-suspense-wt0q7k?file=/src/App.vue

这里有一个深度嵌套的组件,它需要整整5秒来加载,阻塞了整个UI,尽管大多数组件加载完成的时间要早得多。

但对我们来说有一个解决方案

通过进一步嵌套第二个Suspense组件,我们可以在等待这个组件完成加载时显示应用程序的其他部分。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

<template>

  <button @click=reload>Reload page</button>

  <Suspense>

    <WithSuspense :time=2000>

      <WithSuspense :time=1500 />

      <WithSuspense :time=1200>

        <WithSuspense :time=1000 />

 

                <!-- Nest a second Suspense component -->

        <Suspense>

          <WithSuspense :time=5000 />

          <template #fallback>

            <Spinner />

          </template>

        </Suspense>

      </WithSuspense>

    </WithSuspense>

 

    <template #fallback>

      <Spinner />

    </template>

  </Suspense>

</template>

将其包裹在第二个Suspense组件中,使其与应用程序的其他部分隔离。Suspense组件本身是一个同步组件,所以当它的父级组件被加载时,它就会被加载。

然后它将显示它自己的 fallback 内容,直到5秒结束。

222330h95ul897x19uzl7u.gif

通过这样做,我们可以隔离应用程序中加载较慢的部分,减少我们的首次交互时间。在某些情况下,这可能是必要的,特别是当你需要避免异步瀑布时。

从功能的角度来看,这也是有意义的。你的应用程序的每个功能或 部分都可以被包裹在它自己的Suspense组件中,所以每个功能的加载都是一个单一的逻辑单元。

当然,如果你用 Suspense 包装每一个组成部分,我们就会回到我们开始的地方。我们可以选择以任何最合理的方式来批处理我们的加载状态。

使用占位符的 Suspense

事例地址: https://codesandbox.io/s/placeholders-and-suspense-k5uzw0?

与其使用单一的 spinner,占位符组件往往可以提供更好的体验。

222330dr8rarigtoonn0vr.gif

这种方式向用户展示将要展示的内容,并让他们在界面渲染前有一种期待的感觉。这是 spinner 无法做到的。

可以说--它们很时髦,看起来很酷。因此,我们重构代码,使用占位符方式:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

<template>

  <button @click=reload>Reload page</button>

  <Suspense>

    <WithSuspense :time=2000>

      <WithSuspense :time=1500 />

      <WithSuspense :time=1200>

        <WithSuspense :time=1000 />

 

        <Suspense>

          <WithSuspense :time=5000 />

          <template #fallback>

            <laceholder />

          </template>

        </Suspense>

      </WithSuspense>

    </WithSuspense>

    <template #fallback>

      <!-- 这里,复制实际数据的形状  -->

      <laceholder>

        <laceholder />

        <laceholder>

          <laceholder />

          <laceholder />

        </Placeholder>

      </Placeholder>

    </template>

  </Suspense>

</template>

我们安排了这些Placeholder组件,并对它们进行了风格化处理,使它们看起来与WithSuspense组件完全一样。这提供了一个在加载和装载状态之间的无缝过渡。

在演示中,Placeholder组件在背景上给我们提供了一个CSS动画,以创造一个脉动的效果:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

.fast-gradient {

  background: linear-gradient(

    to right,

    rgba(255, 255, 255, 0.1),

    rgba(255, 255, 255, 0.4)

  );

  background-size: 200% 200%;

  animation: gradient 2s ease-in-out infinite;

}

 

@keyframes gradient {

  0% {

    background-position: 0% 50%;

  }

  50% {

    background-position: 100% 50%;

  }

  100% {

    background-position: 0% 50%;

  }

}

总结

爆米花的加载状态是非常明显的,会伤害用户体验。

幸运的是,Suspense 是一个很棒的新特性,它为我们在Vue应用程序中协调加载状态提供了很多选择。

然而,在写这篇文章的时候,Suspense仍然被认为是实验性的,所以要谨慎行事。关于它的状态的最新信息,请参考文档。


全部评论 0

本篇文章带大家深入了解一下Vue3 Suspense,聊聊Suspense是什么、能干什么,以及如何使用它,希望对大家有所帮助!


222330oy2omop7nlvzo27o.jpg

Suspense 不是你想的那样。是的,它帮助我们处理异步组件,但它的作用远不止于此。(学习视频分享:vue视频教程

Suspense 允许我们协调整个应用程序的加载状态,包括所有深度嵌套的组件。而不是像一个爆米花用户界面一样,到处都是 loading,组件突然奔的一下到位

222330dri3ciijh9g3y3zb.gif

有了 Suspense, 我们可以有一个单一的、有组织的系统,一次性加载所有东西。

222330u9a0qp75tf7fnafq.gif

而且,Suspense 也给我们提供了细粒度的控制,所以如果需要的话,我们可以在两者之间实现一些东西。

在这篇文章中,我们将学到很多关于 Suspense 的知识--它是什么,能干什么,以及如何使用它。

首先,我们将仔细看看这些爆米花界面。然后,在看看如何使用Suspense来解决这些问题。之后,尝试通过在整个应用程序中嵌套Suspense来获得更精细的控制。最后,简单看看如何使用占位符来丰富我们的用户界面。

爆米花UI-- Suspense 之前

事例地址:https://codesandbox.io/s/uncoordinated-loading-before-suspense-srh8ll?file=/src/App.vue

如果没有 Suspense,每个组件都必须单独处理其加载状态。

222330jtoyvykozzaatsf2.gif

这可能导致一些相当糟糕的用户体验,多个加载按钮和内容突然出现在屏幕上,就像你在制作爆米花一样。

虽然,我们可以创建抽象组件来处理这些加载状态,但这比使用Suspense要困难得多。有一个单一的点来管理加载状态,比每个组件做自己的事情要容易维护得多。

在事例中,我们使用BeforeSuspense组件来模拟一个内部处理加载状态的组件。把它命名为BeforeSuspense,因为一旦我们实现了Suspense,我们就会把它重构为WithSuspense组件。

BeforeSuspense.vue

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

<template>

    <div class=async-component :class=!loading && loaded>

      <Spinner v-if=loading />

      <slot />

    </div>

</template>

 

<script setup>

import { ref } from vue

import Spinner from ./Spinner.vue

 

const loading = ref(true)

const { time } = defineProps({

  time: {

    type: Number,

    default: 2000

  }

})

 

setTimeout(() => (loading.value = false), time)

</script>

一开始设置 loading 为 true,所以显示 Spinner 组件。然后,当setTimeout完成后,将 loading 设置为 false,隐藏 Spinner 并使组件的背景为绿色。

在这个组件中,还包括一个 slot ,这样其它组件就可以套在 BeforeSuspense 组件中了:

1

2

3

4

5

6

7

8

9

10

<template>

    <button @click=reload>Reload page</button>

    <BeforeSuspense time=3000>

      <BeforeSuspense time=2000 />

      <BeforeSuspense time=1000>

        <BeforeSuspense time=500 />

        <BeforeSuspense time=4000 />

      </BeforeSuspense>

    </BeforeSuspense>

</template>

没有什么太花哨的东西。只是一些嵌套的组件,有不同的时间值传递给它们。

下面,我们来看看怎么通过使用 Suspense 组件来改进这个爆米花用户界面。

Suspense

事例地址:https://codesandbox.io/s/coordinated-loading-with-suspense-b6dcbi?file=/src/App.vue

现在,我们使用Suspense来处理这些乱七八糟的东西,并将其变成一个更好的用户体验。

不过,首先我们需要快速了解一下Suspense到底是什么

Suspense 基础知识

以下是 Suspense 部分的基本结构

1

2

3

4

5

6

7

<Suspense>

  <!-- Async component here -->

 

  <template #fallback>

      <!-- Sync loading state component here -->

  </template>

</Suspense>

为了使用Suspense,把异步组件放入 default 槽,把回退加载状态放入 fallback 槽。

异步组件是以下两种情况之一:

  • 一个带有async setup函数的组件,该组件返回一个Promise,或者在script setup中使用顶级await

  • 使用 defineAsyncComponent 异步加载的组件

无论哪种方式,我们最终都会得到一个开始未解决 的 Promise ,然后最终得到解决。

当该 Promise 未被解决时,Suspense 组件将显示 fallback 槽中的内容。然后,当Promise 被解决时,它会在 default 槽中显示该异步组件。

注意: 这里没有错误处理路基。起初我以为有,但这是对悬念的一个常见误解。如果想知道是什么导致了错误。可以使用onErrorCaptured钩子来捕捉错误,但这是一个独立于Suspense的功能。

现在我们对Suspense有了一些了解,让我们回到我们的演示应用程序。

管理异步依赖关系

为了让Suspense管理我们的加载状态,首先需要将BeforeSuspense组件转换成一个异步组件

我们将它命名为 WithSuspense,内容如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

<template>

  <div class=async-component loaded>

    <!-- 这里不需要一个 Spiner 了,因为加载是在根部处理的 -->

    <slot />

  </div>

</template>

 

<script setup>

const { time } = defineProps({

  time: {

    type: Number,

    required: true

  }

})

// 加入一个延迟,以模拟加载数据

await new Promise(resolve => {

  setTimeout(() => {

    resolve()

  }, time)

})

</script>

我们已经完全删除了加载状态的Spinner,因为这个组件不再有加载状态了。

因为这是一个异步组件,setup 函数直到它完成加载才会返回。该组件只有在 setup 函数完成后才会被加载。因此,与BeforeSuspense组件不同,WithSuspense组件内容在加载完毕之前不会被渲染。

这对任何异步组件来说都是如此,不管它是如何被使用的。在setup函数返回(如果是同步的)或解析(如果是异步的)之前,它不会渲染任何东西。

有了WithSuspense组件,我们仍然需要重构我们的App组件,以便在Suspense组件中使用这个组件。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<template>

  <button @click=reload>Reload page</button>

  <Suspense>

    <WithSuspense :time=2000>

      <WithSuspense :time=1500 />

      <WithSuspense :time=1200>

        <WithSuspense :time=1000 />

        <WithSuspense :time=5000 />

      </WithSuspense>

    </WithSuspense>

 

    <template #fallback>

      <Spinner />

    </template>

  </Suspense>

</template>

结构和之前一样,但这次是在 Suspense 组件的默认槽中。我们还加入了 fallback 槽,在加载时渲染我们的Spinner组件。

在演示中,你会看到它显示加载按钮,直到所有的组件都加载完毕。只有在那时,它才会显示现在完全加载的组件树。

异步瀑布

如果你仔细注意,你会注意到这些组件并不像你想象的那样是并联加载的。

总的加载时间不是基于最慢的组件(5秒)。相反,这个时间要长得多。这是因为Vue只有在父异步组件完全解析后才会开始加载子组件。

你可以通过把日志放到WithSuspense组件中来测试这一点。一个在安装开始跟踪安装,一个在我们调用解决之前。

最初使用BeforeSuspense组件的例子中,整个组件树被挂载,无需等待,所有的 异步 操作都是并行启动的。这意味着Suspense有可能通过引入这种异步瀑布而影响性能。所以请记住这一点。

嵌套 Suspense 以隔离子树

事例地址:https://codesandbox.io/s/nesting-suspense-wt0q7k?file=/src/App.vue

这里有一个深度嵌套的组件,它需要整整5秒来加载,阻塞了整个UI,尽管大多数组件加载完成的时间要早得多。

但对我们来说有一个解决方案

通过进一步嵌套第二个Suspense组件,我们可以在等待这个组件完成加载时显示应用程序的其他部分。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

<template>

  <button @click=reload>Reload page</button>

  <Suspense>

    <WithSuspense :time=2000>

      <WithSuspense :time=1500 />

      <WithSuspense :time=1200>

        <WithSuspense :time=1000 />

 

                <!-- Nest a second Suspense component -->

        <Suspense>

          <WithSuspense :time=5000 />

          <template #fallback>

            <Spinner />

          </template>

        </Suspense>

      </WithSuspense>

    </WithSuspense>

 

    <template #fallback>

      <Spinner />

    </template>

  </Suspense>

</template>

将其包裹在第二个Suspense组件中,使其与应用程序的其他部分隔离。Suspense组件本身是一个同步组件,所以当它的父级组件被加载时,它就会被加载。

然后它将显示它自己的 fallback 内容,直到5秒结束。

222330h95ul897x19uzl7u.gif

通过这样做,我们可以隔离应用程序中加载较慢的部分,减少我们的首次交互时间。在某些情况下,这可能是必要的,特别是当你需要避免异步瀑布时。

从功能的角度来看,这也是有意义的。你的应用程序的每个功能或 部分都可以被包裹在它自己的Suspense组件中,所以每个功能的加载都是一个单一的逻辑单元。

当然,如果你用 Suspense 包装每一个组成部分,我们就会回到我们开始的地方。我们可以选择以任何最合理的方式来批处理我们的加载状态。

使用占位符的 Suspense

事例地址: https://codesandbox.io/s/placeholders-and-suspense-k5uzw0?

与其使用单一的 spinner,占位符组件往往可以提供更好的体验。

222330dr8rarigtoonn0vr.gif

这种方式向用户展示将要展示的内容,并让他们在界面渲染前有一种期待的感觉。这是 spinner 无法做到的。

可以说--它们很时髦,看起来很酷。因此,我们重构代码,使用占位符方式:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

<template>

  <button @click=reload>Reload page</button>

  <Suspense>

    <WithSuspense :time=2000>

      <WithSuspense :time=1500 />

      <WithSuspense :time=1200>

        <WithSuspense :time=1000 />

 

        <Suspense>

          <WithSuspense :time=5000 />

          <template #fallback>

            <laceholder />

          </template>

        </Suspense>

      </WithSuspense>

    </WithSuspense>

    <template #fallback>

      <!-- 这里,复制实际数据的形状  -->

      <laceholder>

        <laceholder />

        <laceholder>

          <laceholder />

          <laceholder />

        </Placeholder>

      </Placeholder>

    </template>

  </Suspense>

</template>

我们安排了这些Placeholder组件,并对它们进行了风格化处理,使它们看起来与WithSuspense组件完全一样。这提供了一个在加载和装载状态之间的无缝过渡。

在演示中,Placeholder组件在背景上给我们提供了一个CSS动画,以创造一个脉动的效果:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

.fast-gradient {

  background: linear-gradient(

    to right,

    rgba(255, 255, 255, 0.1),

    rgba(255, 255, 255, 0.4)

  );

  background-size: 200% 200%;

  animation: gradient 2s ease-in-out infinite;

}

 

@keyframes gradient {

  0% {

    background-position: 0% 50%;

  }

  50% {

    background-position: 100% 50%;

  }

  100% {

    background-position: 0% 50%;

  }

}

总结

爆米花的加载状态是非常明显的,会伤害用户体验。

幸运的是,Suspense 是一个很棒的新特性,它为我们在Vue应用程序中协调加载状态提供了很多选择。

然而,在写这篇文章的时候,Suspense仍然被认为是实验性的,所以要谨慎行事。关于它的状态的最新信息,请参考文档。


热门推荐
您需要登录后才可以回帖 立即登录
说说你的想法......
0
0
0
返回顶部