Still experimental in Vue3 What is Suspense?

Search [Great Move to the World] on WeChat, and I will share with you the front-end industry trends, learning paths, etc. as soon as possible.
This article GitHub https://github.com/qq449245884/xiaozhi It has been included, and there are complete test sites, materials and my series of articles for interviews with first-line manufacturers.

Suspense is not what you think it is. Yes, it helps us deal with async components, but it does much more than that.

Suspense allows us to coordinate the loading state of the entire application, including all deeply nested components. Instead of being like a popcorn user interface, loading is everywhere, and components suddenly rush into place

With Suspense, we can have a single, organized system that loads everything at once.

Also, Suspense gives us fine-grained control, so we can implement something in between if needed.

In this article, we'll learn a lot about Suspense -- what it is, what it can do, and how to use it.

First, we'll take a closer look at these popcorn interfaces. Then, look at how to use Suspense to solve these problems. After that, try to get more fine-grained control by nesting Suspense throughout your application. Finally, a quick look at how we can use placeholders to enrich our user interface.

Popcorn UI -- Before Suspense

Case address: https://codesandbox.io/s/unco...

Without Suspense, each component would have to handle its loading state individually.

This can lead to some pretty bad user experiences, with multiple loading buttons and content popping up on the screen like you're making popcorn.

Although, we could create abstract components to handle these loading states, but this is much more difficult than using Suspense. Having a single point to manage loading state is much easier to maintain than each component doing its own thing.

In the example, we use the BeforeSuspense component to simulate a component that internally handles the loading state. Name it BeforeSuspense because once we implement Suspense we'll refactor it into a WithSuspense component.

BeforeSuspense.vue

<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>

Initially set loading to true so the Spinner component is displayed. Then, when the setTimeout finishes, set loading to false, hiding the Spinner and making the background of the component green.

In this component, a slot is also included so that other components can be nested in the BeforeSuspense component:

<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>

Nothing too fancy. Just some nested components with different time values ​​passed to them.

Next, let's see how to improve this popcorn user interface by using the Suspense component.

Suspense

Case address: https://codesandbox.io/s/coor...

Now, we use Suspense to take that mess and turn it into a better user experience.

First, though, we need a quick look at what exactly Suspense is

Suspense Basics

The following is the basic structure of the Suspense section

<Suspense>
  <!-- Async component here -->

  <template #fallback>
      <!-- Sync loading state component here -->
  </template>
</Suspense>

To use Suspense, put asynchronous components into the default slot and fallback loaded state into the fallback slot.

An asynchronous component is one of two things:

  • A component with an async setup function that returns a Promise, or use top-level await in script setup
  • Components loaded asynchronously using defineAsyncComponent

Either way, we'll end up with a Promise that starts out unresolved and then eventually gets resolved.

When the Promise is unresolved, the Suspense component will display the contents of the fallback slot. Then, when the Promise is resolved, it displays that async component in the default slot.

NOTE: There is no error handling subgrade here. At first I thought there was, but that's a common misconception about suspense. If wondering what is causing the error. Errors can be caught using the onErrorCaptured hook, but this is a feature independent of Suspense.

Now that we know a little about Suspense, let's go back to our demo application.

Manage asynchronous dependencies

In order for Suspense to manage our loading state, we first need to convert the BeforeSuspense component into an asynchronous component

Let's name it WithSuspense, and it reads as follows:

<template>
  <div class="async-component loaded">
    <!-- don't need one here Spiner , since loading is handled at the root -->
    <slot />
  </div>
</template>

<script setup>
const { time } = defineProps({
  time: {
    type: Number,
    required: true
  }
})
// Add a delay to simulate loading data
await new Promise(resolve => {
  setTimeout(() => {
    resolve()
  }, time)
})
</script>

We have completely removed the loading state Spinner, as this component no longer has a loading state.

Because this is an asynchronous component, the setup function doesn't return until it finishes loading. The component will only be loaded after the setup function completes. Therefore, unlike the BeforeSuspense component, the WithSuspense component content will not be rendered until it has loaded.

This is true for any asynchronous component, no matter how it is used. It won't render anything until the setup function returns (if synchronous) or resolves (if asynchronous).

With the WithSuspense component, we still need to refactor our App component to use this component in the Suspense component.

<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>

The structure is the same as before, but this time in the default slot of the Suspense component. We also added a fallback slot to render our Spinner component on load.

In the demo, you'll see it display the loading button until all components are loaded. Only then will it show the now fully loaded component tree.

asynchronous waterfall

If you pay close attention, you'll notice that the components aren't loaded in parallel as you might think.

The total load time is not based on the slowest component (5 seconds). On the contrary, this time is much longer. This is because Vue will only start loading child components after the parent async component has fully resolved.

You can test this by putting logging into the WithSuspense component. One to track the install at the start of the install, and one before we call resolve.

In the original example using BeforeSuspense components, the entire component tree is mounted without waiting, and all "async" operations are started in parallel. This means that Suspense has the potential to impact performance by introducing this asynchronous waterfall. So please keep that in mind.

Nest Suspense to isolate subtrees

Case address: https://codesandbox.io/s/nest...

Here's a deeply nested component that takes a full 5 seconds to load, blocking the entire UI, even though most components finish loading much sooner.

But for us there is a solution 😅

By further nesting a second Suspense component, we can display other parts of the application while waiting for this component to finish loading.

<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>

Wrap it in a second Suspense component to isolate it from the rest of the application. The Suspense component itself is a synchronous component, so it will be loaded when its parent component is loaded.

It will then display it's own fallback content until the 5 seconds are up.

By doing this, we can isolate the slower loading parts of the application, reducing our time to first interaction. In some cases, this may be necessary, especially if you need to avoid asynchronous waterfalls.

It also makes sense from a functional standpoint. Each function or "section" of your application can be wrapped in its own Suspense component, so each function is loaded as a single logical unit.

Of course, if you wrap each component with "Suspense", we're right back where we started. We can choose to batch our loading state in whatever way makes the most sense.

Suspense using placeholders

Case address: https://codesandbox.io/s/plac...

Rather than using a single spinner, placeholder components often provide a better experience.

This way shows the user what will be displayed and gives them a sense of anticipation before the interface renders. This is something spinner s cannot do.

Let's just say - they're trendy and look cool. Therefore, we refactor the code to use placeholders:

<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>
            <Placeholder />
          </template>
        </Suspense>
      </WithSuspense>
    </WithSuspense>
    <template #fallback>
      <!-- Here, copy the shape of the actual data  -->
      <Placeholder>
        <Placeholder />
        <Placeholder>
          <Placeholder />
          <Placeholder />
        </Placeholder>
      </Placeholder>
    </template>
  </Suspense>
</template>

We arranged these Placeholder components and stylized them so that they look exactly like the WithSuspense component. This provides a seamless transition between loading and loaded states.

In the demo, the Placeholder component gives us a CSS animation on the background to create a pulsating effect:

.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%;
  }
}

Summarize

The loading state of the popcorn is very obvious and hurts the user experience.

Luckily, Suspense is a great new feature that gives us a lot of options for coordinating loading state in our Vue applications.

However, at the time of this writing, Suspense is still considered experimental, so proceed with caution. For the latest information on its status, please refer to the documentation.

Bugs that may exist during editing cannot be known in real time. In order to solve these bugs afterwards, a lot of time was spent on log debugging. By the way, I would like to recommend a useful bug monitoring tool for everyone. Fundebug.

Author: Michael Thiessen Translator: Xiaozhi Source: vueschool

original: https://vueschool.io/articles...

communicate with

Have dreams, have dry goods, search on WeChat [Daqian World] Follow this dishwashing wisdom who is still washing dishes in the early morning.

This article GitHub https://github.com/qq449245884/xiaozhi It has been included, and there are complete test sites, materials and my series of articles for interviews with first-line manufacturers.

Tags: Javascript css3 TypeScript css

Posted by Salkcin on Fri, 30 Dec 2022 10:01:50 +0300