Use defineAsyncComponent to delay loading components in Vue.js

Using  vue  3's defineAsyncComponent feature allows us to lazy load components. This means they are only loaded from the server when needed. This is a great way to improve the initial page load, as our app will load in smaller chunks without having to load every component on page load.

In this tutorial, we'll learn what defineAsyncComponent is all about, and look at an example that defers the loading of a popup until our application needs it.

Alright, let's get started.

what is defineAsyncComponent

// SOURCE: https://v3.vuejs.org/guide/component-dynamic-async.html
const AsyncComp = defineAsyncComponent(
  () =>
    new Promise((resolve, reject) => {
      resolve({
        template: '<div>I am async!</div>'
      })
    })
)

defineAsyncComponent accepts a factory function that returns a Promise . This Promise should resolve when we successfully fetch the component from the server, and reject if there is an error.

To use it, we have to import it from Vue before we can use it in the rest of the script.

We can also easily add Vue components from other files using import in the factory function .

import { defineAsyncComponent } from "vue" 

// 简单使用
const LoginPopup = defineAsyncComponent(() => import("./components/LoginPopup.vue"))

This is the easiest way to use defineAsyncComponent , but we can also pass in a full options object, configuring several more advanced parameters.

// with options 
const AsyncPopup = defineAsyncComponent({ 
  loader: () => import("./LoginPopup.vue"),
  loadingComponent: LoadingComponent, /* 在加载时显示 */
  errorComponent: ErrorComponent, /* 显示是否有错误 */
  delay: 1000, /* 在显示加载组件之前延迟毫秒 */
  timeout: 3000 /* 这个毫秒之后的超时 */
})

Personally, I find myself using the first shorter syntax more often and it works for most of my use cases, but it's all up to you.

Simple as that, let's get into our example.

Lazy loading popup component using defineAsyncComponent

In this example, we will use a login popup that is triggered by a button click.

We don't need our app to load this component whenever our app loads, because it's only needed when the user performs a specific action.

So this is what our login component looks like, it just creates a popup by blacking out the rest of the screen with position: fixed and has some inputs and a submit button.

<template>
  <div class="popup">
    <div class="content">
      <h4> Login to your account </h4>
      <input type="text" placeholder="Email" />
      <input type="password" placeholder="Password" />
      <button> Log in </button>
    </div>
  </div>
</template>

<script>
</script>

<style scoped>
.popup {
  position: fixed;
  width: 100%;
  top: ; 
  left: ;
  height: 100%;
  background-color: rgba(, , , 0.2);
  display: flex;
  justify-content: center;
  align-items: center;
}
.content {
   min-width: 200px;
   width: 30%;
   background: #fff;
   height: 200px;
   padding: 10px;
   border-radius: 5px;
}
input[type="text"], input[type="password"] {
  border: ;
  outline: ;
  border-bottom: 1px solid #eee;
  width: 80%;
  margin:  auto;
  font-size: 0.5em;
}
button {
  border: ;
  margin-top: 50px;
  background-color:#8e44ad;
  color: #fff;
  padding: 5px 10px;
  font-size: 0.5em;
}
</style>

Instead of importing it like we usually do and including it in our components option.

<!-- "Standard" way of doing things -->
<template>
  <button @click="show = true"> Login </button>
  <login-popup v-if="show" />
</template>

<script>
import LoginPopup from './components/LoginPopup.vue'
export default {
  components: { LoginPopup },
  data() {
    return {
      show: false
    }
  }
}
</script>

We could instead use defineAsyncComponent to load it only when needed (meaning button click and toggle our v-if)

<!-- Use defineAsyncComponent  -->
<template>
  <button @click="show = true"> Login </button>
  <login-popup v-if="show" />
</template>

<script>
import { defineAsyncComponent } from 'vue'
export default {
  components: { 
    "LoginPopup" : defineAsyncComponent(() => import('./components/LoginPopup.vue'))
  },
  data() {
    return {
      show: false
    }
  }
}
</script>

While this may look the same as we use our application, let's examine Elements > Network to understand this small but important difference.

If we don't use defineAsyncComponent, we'll see our application get LoginPopup.vue from the server once our page loads. While this might not be the biggest performance issue in this example, it still slows down loading, and it really adds up if we have dozens of components doing this.

However, if we look at the same tab with defineAsyncComponent, we'll notice that when our page loads, LoginPopup.vue is gone, because it hasn't loaded yet.

But as soon as we click our button and tell our app to show our popup, that's when it loads from the server and we can see it in the network tab.

This helps us achieve optimal performance. We only want to load the required components on the initial load of our page. Conditionally rendered components are often not needed when our page loads, so why should our application load them?

How to use the asynchronous setup function

Regardless of whether we use defineAsyncComponent for lazy loading, any component with async setup must be wrapped with <Suspense> .

In a nutshell, creating an async setup function is an option for us to have our component wait for some API call or other async action before rendering.

This is our component with async setup. It uses setTimeout() to simulate an API call.

<template>
  <div class="popup">
    <div class="content">
      <p> Loaded API: {
   
   { article }} </p>
      <h4> Login to your account </h4>
      <input type="text" placeholder="Email" />
      <input type="password" placeholder="Password" />
      <button> Log in </button>
    </div>
  </div>
</template>

<script>
const getArticleInfo = async () => {
  // wait 3 seconds to mimic API call
  await new Promise(resolve => setTimeout(resolve, 1000));
  const article = {
    title: 'My Vue 3 Article',
    author: 'Matt Maribojoc'
  }
  return article
}
export default {
  async setup() {
    const article = await getArticleInfo()
    console.log(article)
    return {
      article
    }
  }
}
</script>

We can import it into our component with or without defineAsyncComponent

import LoginPopup from './components/LoginPopup.vue'
// OR 
const LoginPopup = defineAsyncComponent(() => import("./components/LoginPopup.vue"))

But if we want it to render in our template, we need to wrap it in a Suspense element. This will wait for our setup function to resolve before trying to render our component.

<template>
  <button @click="show = true"> Login </button>
  <Suspense v-if="show">
    <template #default>
      <login-popup  />
    </template>
    <template #fallback>
      <p> Loading... </p>
    </template>
  </Suspense>
</template>

This is the result. The user will see "Loading...", then after 3 seconds (the hardcoded value for our setTimeout), our component will render.

By default, all components we define with defineAsyncComponent are pauseable.

This means that if a component has Suspense in its parent chain, it will be considered an asynchronous dependency of that Suspense. Our component's load, error, delay, and timeout options will be ignored and instead handled by Suspense.

final thoughts

defineAsyncComponent is beneficial when creating large projects with dozens of components. When we get into lazy loading components, we can have faster page load times, improve user experience, and ultimately increase your app's retention and conversion rates.

Guess you like

Origin blog.csdn.net/z591102/article/details/120011175
Recommended