4
头图

Using the defineAsyncComponent feature of Vue 3 allows us to lazily load components. This means that they are only loaded from the server when needed.

This is a great way to improve the initial page load, because our application will load in smaller chunks instead of having to load every component when the page loads.

In this tutorial, we will learn defineAsyncComponent and look at an example that defers the loading of a pop-up window until our application needs it.

Okay, 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 Promise. When we successfully get the component from the server, the Promise should be resolve , if there is an error, it will be reject .

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

import in the factory function to easily add Vue components from other files.

import { defineAsyncComponent } from "vue" 

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

This is defineAsyncComponent , but we can also pass in a complete option object and configure 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.

It's that simple, let's get into our example.

Use defineAsyncComponent to delay loading popup components

In this example, we will use a login pop-up window triggered by a button click.

Whenever our application loads, we don't need our application to load this component, because it is only needed when the user performs a specific operation.

So this is what our login component looks like, it just position: fixed out the rest of the screen with 061177108a9c8d, and has some input 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 as 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 can instead use defineAsyncComponent to load it only when needed (meaning click the button and switch 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>

Although this may look the same when we use our application, let's check Element> Network to understand this small but important difference.

If we don't use defineAsyncComponent , once our page loads, we will see our application get LoginPopup.vue from the server. Although in this example, this may not be the biggest performance issue, it will still slow down the loading speed, and if we have dozens of components doing this, it will really add up.

However, if we use defineAsyncComponent view the same tab, we will notice that when our page loads, LoginPopup.vue missing because it has not been loaded yet.

But once we click our button and tell our application to display our pop-up window, then it will be loaded from the server and we can see it in the network tab.

This helps us achieve the best performance. We only want to load the required components when our page is initially loaded. Conditionally rendered components are often not needed when our page loads, so why let our application load them?

How to use the asynchronous setup function

Regardless of whether we use defineAsyncComponent lazy loading, any component with asynchronous settings must be packaged <Suspense>

In short, creating an asynchronous setting function is one of our choices, allowing our components to wait for some API calls or other asynchronous actions before rendering.

This is our component with asynchronous settings. It uses setTimeout() simulate API calls.

<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 be rendered in our template, we need to wrap it in a Suspense element. This will wait for our setup function to resolve before attempting 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...", and after 3 seconds (the hard-coded value of our setTimeout), our component will render.

By default, all components defined by defineAsyncComponent are pauseable.

This means that if a component has Suspense in its parent chain, it will be treated as an asynchronous dependency of that Suspense. The loading, error, delay, and timeout options of our component will be ignored, and Suspense will handle it instead.

Final thoughts

defineAsyncComponent when creating large projects with dozens of components. When we enter lazily loading components, we can have faster page load times, improve user experience, and ultimately increase the retention and conversion rate of your application.

I want to know your opinion on this feature. If you have already used it in your app, please let me know in the comments below.


杭州程序员张张
11.8k 声望6.7k 粉丝

Web/Flutter/独立开发者/铲屎官