A year ago, my first task as a frontend developer was to rebuild a learning management system for an online education website of our company. At that time I only know little about framework and I was asked to build the project with Vue. It was stressful, but luckily, Vue and Vuetify are easy to use, so everything went great, and I can tell that my supervisors are satisfied with my work.
But there was one thing annoyed me that I kept in my mind. I had a long list with checkboxes rendered by v-for or Vuetify table, the response of checkbox lag badly. My solution to it was making pagination, but sometimes it's easier to use when the whole list is shown. The solution isn't perfect and a bigger issue is that I don't know what's the cause.
Later, I have another project that needs to render a long list and checkboxes. Again, I ran into the same issue. I use different UI libraries for these two projects, so I was thinking maybe it's because the checkboxes components that causes bad performance. So I decided to make one myself, I made a very simple Gmail like checkbox and it solves the issue. At least, it looks like.
Until recently, I watched a tutorial "Are You Following This Vue Best Practice?" on Youtube. Before that, I was not aware of that props update will cause child components to rerender and update even there is nothing to change. So best practice is we need to make props passing to be as stable as possible.
An ideal checkbox should react to change instantly. This is a checkbox from Vuetify 3.
Checkbox can lag as the length of a list grows. When we use
v-model on checkboxes binding them to an array named
selected changes when a checkbox changes. We already know that props update will trigger child component update, so when
selected changes, it will then trigger all checkboxes to update.
If your machine is fast, you may not experience the issue as shown in the image.
To avoid the prop passed to checkboxes keep changing, instead of binding checkbox's
v-model to a same prop, we can create a new list
localList that add a
isChecked property to each item, and bind each checkbox to their respective
item.isChecked, thus we limit the props change. We can then make a computed value to compute the
Why is this still happening after we have limited the props change?
Because now we are changing the list for
v-for, although we only change
item.isChecked of one item, Vue creates VNodes for each item in the
localList and diffing them. Since the checkbox components from UI libraries are complex, it takes some time to compute.
This is when we need v-memo. By specifying
v-memo, We can explicitly tell Vue when to update the nodes in
v-for and skip those who don't need to be patched at all.
v-memo accept an array of dependences, it can only be used with
v-for on the same element. if all dependencies of a node in
v-memo remain the same, that node won't update.
<div v-for="item in localList" v-memo=[item.isChecked] :key="item.id" > ... </div>
This is saying if
item.isChecked of this node doesn't change, it don't need to be patched. Without specifying
v-memo, one item in
localList changes can cause Vue to update all nodes. This is different than binding all checkbox to a same prop (an array), but they both affect performance.
v-memo=[item.isChecked] compared to last example.
v-memo allows us to tweak the performance when we need to render a long list especially a interactive one. v-memo is a relatively new feature and it does not exist in Vue 2. In my case, I cannot use v-memo to update my first project, but I can still replace the complex checkbox component with my own simple checkbox component. In fact, if the checkbox component is simple, binding v-model to an array won't be a problem. The main idea of this article is to find out what could cause a component to update and sneakily becomes a performance issue.