You’ve probably come across times when you need to wait several seconds for an API call to finish and get some response data. Sometimes, even minutes. Those are mostly the occasions when you want to get a really huge amount of data at once from the server to your web application. If you’re a Vue developer, you probably know that Vue excels in performance in comparison to React and Angular. But at times, even Vue’s stability and performance takes a beating. Here, I’m not talking about a few KB’s of data but quite a few MB’s of data.
Difference between v-for loop and a virtual list?
People have accepted using the v-for loop as the traditional way to render a list in the Vue template. All the popular, reactive frameworks use the same mechanism, from the likes of React, Angular and Svelte. But this is only feasible when you don’t have a huge payload of data. As per my experience, you can easily load a list containing up to 500 KB of data, but beyond that, you’ll run into performance issues. Say, you gathered some 200 KB of data from an API call in 1.5 seconds, however, it might take another whole second to render the data on the app. That makes the user wait for a total of 2.5 seconds to see the actual data. And there’s absolutely nothing you can do here. You might question what about paging? Well, this is for the case when you don’t have paging on the server. There are situations where you avoid paging in order to prevent multiple API hits within a short span of time.
Why is the v-for loop a bit slow?
When you render a large amount of data at once, Vue takes a while to display the same to the user. And there is a really good reason why. This performance penalty is based on the very fact that Vue is a reactive framework. Therefore, whenever something goes into the v-for loop, Vue listens for changes using the :key attribute. And when you have a large amount of data, say 2000 items for instance, the passive Vue watcher listens for changes in each of those 2000 items.
But wait, what if you don’t want the data to be reactive? What if you don’t want Vue to listen for changes? You don’t want to modify the list once loaded because this list doesn’t contain anything the user wants to change. User just wants to see this list. That’s exactly where virtual lists come into play.
Performance difference between v-for loop and Vue virtual list
I did a small test trying to render 4.5 MB of data using both v-for loop and the Vue virtual list. Below was the performance difference visually. Note that, in both the cases I recorded the time after the API calls finished. That is, the graph you see below only shows the rendering time on the web browser.
As you can see clearly, virtual lists are almost 3x faster than v-for loop lists.
Should you use vue virtual lists everywhere then?
The answer depends on the type of data you want to show. If your list contains too many states and needs to be updated frequently, you probably want to skip this. Virtual lists are not reactive. This is because of the absence of change event listeners (aka watchers). When you render some data using a virtual list, the DOM considers it like a static list and doesn’t expect it to change or self-update. This is in contrary to the sole principle of a reactive framework. However, if you have no option but to render a really large list without paying a performance penalty, you have the answer now.
How to install vue-virtual-scroll-list?
Alright, enough with the chit-chat. If you are serious about using the virtual list for it’s greater performance benefits, here’s how you do it. Considering you already have a Vue project set up, you can follow along. Alternatively, you can jump to the end of this article to get the source code of the example you’re going to see.
First, run the following command with admin access on your command line tool:
npm install vue-virtual-scroll-list
Code language: PHP (php)
This will install Vue virtual list in your project. Check your package.json file and make sure it is at least version 2.3.2.
Next up, in the component where you want to display the list, import virtual list as follows:
<template>
<div>
<h2>Virtual list example</h2>
<virtual-list
style="height: 360px; overflow-y: auto"
scrollable
:data-key="'id'"
:data-sources="items"
:data-component="itemComponent"
/>
</div>
</template>
<script>
import ListItem from "./ListItem";
import VirtualList from "vue-virtual-scroll-list";
export default {
name: "root",
data() {
return {
itemComponent: ListItem,
items: [
{ id: 1, title: "Gautam" },
{ id: 2, title: "Night" },
{ id: 3, title: "Programmer" },
{ id: 4, title: "Gautam" },
{ id: 5, title: "Night" },
{ id: 6, title: "Programmer" },
{ id: 7, title: "Gautam" },
{ id: 8, title: "Night" },
{ id: 9, title: "Programmer" },
{ id: 10, title: "Gautam" },
{ id: 11, title: "Night" },
{ id: 12, title: "Programmer" },
{ id: 13, title: "Gautam" },
{ id: 14, title: "Night" },
{ id: 15, title: "Programmer" },
{ id: 16, title: "Gautam" },
{ id: 17, title: "Night" },
{ id: 18, title: "Programmer" },
{ id: 19, title: "Gautam" },
{ id: 20, title: "Night" },
{ id: 21, title: "Programmer" },
{ id: 22, title: "Gautam" },
{ id: 23, title: "Night" },
{ id: 24, title: "Programmer" },
{ id: 25, title: "Gautam" },
{ id: 26, title: "Night" },
{ id: 27, title: "Programmer" },
{ id: 28, title: "Gautam" },
{ id: 29, title: "Night" },
{ id: 30, title: "Programmer" },
{ id: 31, title: "Gautam" },
{ id: 32, title: "Night" },
{ id: 33, title: "Programmer" },
{ id: 34, title: "Gautam" },
{ id: 35, title: "Night" },
{ id: 36, title: "Programmer" },
{ id: 37, title: "Gautam" },
{ id: 38, title: "Night" },
{ id: 39, title: "Programmer" },
{ id: 40, title: "Gautam" },
{ id: 41, title: "Night" },
{ id: 42, title: "Programmer" },
{ id: 43, title: "Gautam" },
{ id: 44, title: "Night" },
{ id: 45, title: "Programmer" },
{ id: 46, title: "Gautam" },
{ id: 47, title: "Night" },
{ id: 48, title: "Programmer" },
{ id: 49, title: "Gautam" },
{ id: 50, title: "Night" },
{ id: 51, title: "Programmer" },
{ id: 52, title: "Gautam" },
{ id: 53, title: "Night" },
{ id: 54, title: "Programmer" },
{ id: 55, title: "Gautam" },
{ id: 56, title: "Night" },
{ id: 57, title: "Programmer" },
{ id: 58, title: "Gautam" },
{ id: 59, title: "Night" },
{ id: 60, title: "Programmer" },
],
};
},
components: { "virtual-list": VirtualList },
};
</script>
Code language: HTML, XML (xml)
In the above code, first you import virtual list using:
import VirtualList from "vue-virtual-scroll-list";
Code language: JavaScript (javascript)
Then you return the components by referencing it inside the components property on line 87. Next up, you create an item component and import it as well. We’re going to create that item component in a bit. This is the component where you pass in all the data that you want to render using the virtual list. Here, I’ve imported the item component as:
import ListItem from "./ListItem";
Code language: JavaScript (javascript)
And returned the component to the template as follows on line 22:
itemComponent: ListItem
Code language: HTTP (http)
On line 23, you can see I’ve added some hard-coded data and stored it in the items array (state). Of course, you can get the same data from an API call and use a getter inside the computed property to get the desired data instead. You will still be able to reference the items in the virtual list.
Then on line 4, you have the virtual list that is being rendered as a component. There are three important props that we pass into the virtual list:
data-key: This has to be a unique key, here I’m using ‘id’ since it’s unique in my list
data-sources: This is where you pass the list or the array of objects (most likely) that you want to display
data-component: This is the single component that is going to be repeatedly displayed, same as the number of times the length of the list you’ve passed in data-sources
With that being done, let’s now create the item component (where you actually pass the data). Here, I’m naming the file ListItem.vue, you can name it whatever makes sense as per your need. Make sure to create the file in the same directory as the above file.
<template>
<div class="list-style">{{ index }} - {{ source.title }}</div>
</template>
<script>
export default {
name: "item-component",
props: {
index: {
type: Number,
},
source: {
type: Object,
default() {
return {};
},
},
},
};
</script>
<style scoped>
.list-style {
border: 1px solid #eee;
background: #eee;
padding: 10px 8px;
margin: 6px 0;
text-align: left;
}
</style>
Code language: HTML, XML (xml)
In the above file, you get two props objects named index and source.
index: It is just the indexing of all the items you’ve passed via :data-sources
source: It is the object from that array of objects which you’ve passed via :data-sources. For instance, in the above example, the first object I passed was
{ id: 1, title: “Gautam” }.
So, I can just use {{ source.title }} to get the name ‘Gautam’ anywhere in the template.
I’ve additionally added some scoped CSS at the bottom of the component which you might not need, or customize as per your preference.
You can find the finished code from my repos here: