Using a "proper" key in v-for simply circumvents reactivity and breaks performance
Asked Answered
B

0

1

Everywhere (vueschool, in the official docs, here, here, here etc.) it is recommend to use a unique key for each item in an array when using v-for in vue, to quote the official docs and another answer:

To give Vue a hint so that it can track each node's identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item:

I'm working with a large data-table, where we have around 200 rows with ~5 columns being displayed and updated through pagination. I'm, like everywhere told, using a key for each row with the id being the unique key for each item. Each pagination takes a lot of time (around 1s) due to every component being re-mounted which seems to be the expected behavior when the key changes?

Example:

// App.vue
<template>
  <button @click="heros.splice(0, heros.length, ...moreHeros)">Change list</button>
  <button @click="heros.push({name: `Ivy ${count++}`})">Add hero</button>
  <button @click="heros.pop()">Remove hero</button>

  <ul>
    <SuperHero v-for="(hero, index) in heros" :name="hero.name" :key="index"/>
  </ul>
</template>

<script lang="ts" setup>
import { reactive, ref } from 'vue';
import SuperHero from './SuperHero.vue';

const moreHeros = [
  {name: 'Superman'},
  {name: 'Spiderman'}
];

const count = ref(0);
const heros = reactive([
  {name: 'Batman'},
  {name: 'Robin'},
]);
</script>

// SuperHero.vue
<template>
  <li>
    <strong>Name:</strong> {{ name }}
  </li>
</template>

<script lang="ts" setup>
import { onMounted } from 'vue';

defineProps<{
  name: string;
}>();

onMounted(() => console.log('Mounted'));
</script>

If we now replace the list, all items are updated but each one re-mounted because the key changed.

But if we now use index like so:

<ul>
    <SuperHero v-for="(hero, index) in heros" :name="hero.name" :key="index"/>
</ul>

The list is updated as well (through props) plus we get the performance benefit of vue not re-mounting the other components. If we remove an item the key simply doesn't exist anymore so vue un-mounts the component, same for when a new item is added where a new key is introduced.

This doesn't make that much difference in most cases, but the re-rendering part causes major performance issues if you render a large list with 5000 plus components in it where on, page change, the id for every row changes causing vue to un-mount and re-create every single component.

My argument is, all the arguments given here, here and other places to support the usage of key are based on examples where, simply put, reactivity is circumvented by not using ref, watch or any other method - which is a bug and not something that should be fixed by simply re-rendering the whole component.

Why is it still recommended, everywhere, that you should use a unique key instead of index and just make use of reactivity in a proper way? Vue should only mount/un-mount a component if an item is added / removed because in any other cases, just the props for the given component changes which does not need a re-mount.

Playground with example, notice that when using hero.name as key, the component is re-mounted, but when using index just patched. In botch cases it works as expected.

Bensky answered 6/7, 2023 at 9:19 Comment(9)
"If we now add a new item, all items after the position we add the item are re-mounted". That doesn't seem right and is maybe an issue with how your data table updates? playground example with your code: Observe the console when adding a new hero. Only 1 new component mounts with each insert. I did change your button @click to splice heros instead of users since users doesn't exist in your question's code snippet which I figure was a typo. Regardless, will probably need to see more code to fully understand the issueDecurion
Basically if the key doesn't update, it doesn't matter if you're inserting or sorting the data, the component shouldn't rerun its lifecycle hooks. During your actual insert or update process are you copying or reassigning any data or properties, or doing anything besides a simple splice?Decurion
@Decurion my point is, why would it ever rerun its lifecycle hooks, when updating the component is almost every time sufficient except for when a new component is added / removed? It seems like a waste of resource because since components are reactive, shifting the data or replacing an item should update the props of the given component, not re-mount it because the key changes...Bensky
@Decurion I updated my example, the one from vueschool was bad because it was inherently broken already. Here is the updated playground.Bensky
Updating the key is the sole method of rerunning lifecycle hooks, for whatever reasons you might want to do that. It's within your control whether or not you want that to happen. You can have a component be perfectly reactive with updating props and no hooks rerunning as long as the key stays the same. The important thing is that adding a new component to your list shouldn't cause other components to rerun their hooks (unless all your keys are somehow changing). Isn't that the problem you originally described? I don't see that issue reproduced in your playground.Decurion
Sorry I may not be fully understanding things. Adding a brand new component makes sense that it runs its lifecycle hooks.. It's being created so it must run it's created hook. It's being mounted so it must run it's mounted hook. If you want to replace a component and not have it's hooks run, the key must stay the same. That may require using a different key than you're using nowDecurion
@Decurion do you have a link for me stating that this is really just for re-running the lifecycle hooks? I'm just confused that in most cases a different reason is mentioned when it comes to why to use key and what it's for...Bensky
When I said keys are the sole method of rerunning lifecycle hooks, I did not mean it's the sole reason keys exist. The reason they're used with lists is because without the unique ID of a key, Vue will under certain conditions lose track of what's what when list components are moved around or updated. If the components have a unique key, Vue can better track the movement of components and re-use them instead of destroying/creating (causing lifecycle hooks).Decurion
Gotchya @yoduh, thank you for the clarification! I just had hoped that the docs in vue would state that. The only other way of finding out such things would be to inspect the source-code of how vue handles this...Bensky

© 2022 - 2024 — McMap. All rights reserved.