Using Vue 3’s defineAsyncComponent feature allows us to lazily load components. This means that they are only loaded from the server when needed.
This is a good way to improve initial page loading, because our application will load in smaller chunks rather than having to load each component as the page loads.
In this tutorial, we’ll learn all about defineAsyncComponent and look at an example that delays loading a popup window until our application needs it.
All right, let’s get started.
What is a defineAsyncComponent
// SOURCE: https://v3.vuejs.org/guide/component-dynamic-async.html
const AsyncComp = defineAsyncComponent(
() = >
new Promise((resolve, reject) = > {
resolve({
template: '
I am async!
'}})))Copy the code
DefineAsyncComponent accepts a factory function that returns a Promise. The Promise should be resolved when we successfully get the component from the server, and rejected if an error occurs.
To use it, we must import it from Vue before we can use it for 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"
// Easy to use
const LoginPopup = defineAsyncComponent(() = > import("./components/LoginPopup.vue"))
Copy the code
This is the easiest way to use defineAsyncComponent, but we could also pass in a full options object to configure several more advanced parameters.
// with options
const AsyncPopup = defineAsyncComponent({
loader: () = > import("./LoginPopup.vue"),
loadingComponent: LoadingComponent, /* Display */ at load time
errorComponent: ErrorComponent, /* Displays whether there are errors */
delay: 1000./* Delay milliseconds before displaying loaded components */
timeout: 3000 /* The timeout after this millisecond */
})
Copy the code
Personally, I find myself using the first shorter grammar more often, and it works for most of my use cases, but that’s entirely up to you.
That’s it. Let’s go into our example.
Use defineAsyncComponent to lazily load the eject component
In this case, we will use a login pop-up that is triggered by clicking a button.
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, which simply creates a pop-up window by blackening the rest of the screen with Position: Fixed, 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.5 em;
}
button {
border:;margin-top: 50px;
background-color:#8e44ad;
color: #fff;
padding: 5px 10px;
font-size: 0.5 em;
}
</style>
Copy the code
Rather than importing it and including it in our Components options as we normally do.
<! -- "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>
Copy the code
We can use defineAsyncComponent instead to load it only when needed (meaning click the button 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>
Copy the code
While this may look the same when we use our application, let’s examine elements > Network to understand this small but important difference.
If we don’t use defineAsyncComponent, once our page loads, we’ll see our application get loginpopup.vue from the server. While this may not be the biggest performance issue in this case, it still slows down the load, and if we have dozens of components doing this, it really adds up.
However, if we use defineAsyncComponent to look at the same TAB, we’ll notice that when our page loads, loginpopup.vue is missing because it hasn’t loaded yet.
But once we click our button and tell our application to display our popup window, it will load from the server and we can see it in the web TAB.
This helps us achieve optimal performance. We only want to load the required components when our page is initially loaded. Conditionally rendered components are often not needed when our pages load, so why make our applications load them?
How do I use the asynchronous setup function
Any component with asynchronous setting functionality must be wrapped in Suspense whether or not we use defineAsyncComponent lazy loading.
In short, creating an asynchronous setup function is one of our options to make our component wait for some API call or other asynchronous action before rendering.
This is the component that we have asynchronous Settings for. It uses setTimeout() to 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>
Copy the code
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"))
Copy the code
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 parse 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>
Copy the code
This is the result. The user will see “loading……” Then after 3 seconds (the hard-coded value of our setTimeout), our component will render.
By default, all components we defined using defineAsyncComponent are pauseable.
This means that if a component has Suspense in its parent chain, it will be treated as an asynchronous dependency for that component. Loadings, errors, delays, and timeouts for our components are ignored and handled by Suspense instead.
Final thoughts
DefineAsyncComponent is useful when creating large projects with dozens of components. When we move into lazy loading components, we can have faster page load times, improve the user experience, and ultimately improve retention and conversion rates for your application.
I want to know what you think about this feature. If you already use it in your app, let me know in the comments below.
Translated from Learnvue.co by Matt Maribojoc