Here's a small test I made to investigate unnecessary node re-rendering for lists in vue3 (vue2 has the same behavior): https://kasheftin.github.io/vue3-rerender/. That's the source code: https://github.com/Kasheftin/vue3-rerender/tree/master.
I'm trying to understand why vue re-renders already rendered nodes in v-for in some cases. I know (and will provide below) some technics to avoid re-rendering, but for me it's crucial to understand the theory.
For tests I added a dummy v-test directive that just logs when mounted/beforeUnmount hooks triggered.
Test 1
<div v-for="i in n" :key="i">
<div>{{ i }}</div>
<div v-test="log2">{{ log(i) }}</div>
</div>
Result: all the nodes re-rendered when n increases. Why? How to avoid that?
Test 2
Test2.vue:
<RerenderNumber v-for="i in n" :key="i" :i="i" />
RerenderNumber.vue:
<template>
<div v-test="log2">{{ log() }}</div>
</template>
Result: It works correctly. Moving the inner content from test1 to a separate component fixes the issue. Why?
Test 3
<RerenderObject v-for="i in n" :key="i" :test="{ i: { i: { i } } }" />
Result: unnecessary re-rendering. It seems it's not allowed to constuct objects on the fly in cycle before sending it to some child component, probably because {} != {} in JavaScript.
Test 4
<template>
<RerenderNumberStore v-for="item in items" :key="item.id" :item="item" />
</template>
<script>
export default {
computed: {
items () {
return this.$store.state.items
}
},
methods: {
addItem () {
this.$store.commit('addItem', { id: this.items.length, name: `Item ${this.items.length}` })
}
}
}
</script>
Here the simplest vuex store is in use. It works correctly - no unnecessary re-rendering despite item prop is an object.
Test 5
<RerenderNumberStore v-for="item in items" :key="item.id" :item="{ id: item.id, name: item.name }" />
The same as test 4, but item prop restructured - and we get unnecessary re-rendering.
Test 6
Test6.vue:
<RerenderNumberStoreById v-for="item in items" :key="item.id" :item-id="item.id" />
RerenderNumberStoreById.vue:
<template>
<div v-test="log">{{ item.name }}</div>
</template>
<script>
export default {
props: ['itemId'],
computed: {
item () { return this.$store.state.items.find(item => item.id === this.itemId) }
}
}
</script>
Result: unnecessary re-rendering. Why? I Can not find any reason why the behavior differs from test 4. This one is less clear for me - the item computed is not changed in any way when the new item added to items array. It returns the SAME object. It has to be cached, matched to the previous value and do not trigger any update in the DOM.