I have a hard time figuring out a huge performance issue with a component list via v-for
.
Here is my typescript code:
<template>
<template v-for="item in list" :key="item.id">
<TestComponent @mouseenter="hoveredItem = item" @mouseleave="hoveredItem = null" />
</template>
<div v-if="hoveredItem">hovered</div>
</template>
<script lang="ts">
import TestComponent from 'TestComponent.vue';
import { Options, Vue } from 'vue-class-component';
interface IItem {id:number, message:string};
@Options({
props:{},
components:{ TestComponent, }
})
export default class TestView extends Vue {
public list:IItem[] = [];
public hoveredItem:IItem|null = null;
public mounted():void {
for (let i = 0; i < 3; i ) {
this.list.push({ id:i, message:"Message " (i 1), });
}
}
}
</script>
When I roll over an item (see @ mouseeenter), a render()
is triggered on all the items of the list which shouldn't be necessary.
I checked with Vue Devtools
extension that shows these events for every single item of the list :
- render start
- render end
- patch start
- patch end
If i remove the following line, no render/patch is triggered:
<div v-if="hoveredItem">hovered!</div>
If instead of storing the item instance to hoveredItem
i just raise a flag to display that div, i don't have the issue.
If instead of instantiating the <TestComponent>
I use a simple <div>
i don't have the issue.
If I don't use a v-for
but manually instantiate items, I don't have the issue.
If I $emit a custom event from the instead of using native @mouseover
The <TestComponent>
is just that:
<template>
<div>item</div>
</template>
Here is a codesandbox showing the issue of the first example and the fix via an $emit() from the child component https://dh5ldo.csb.app
Do you have any hint on why the first example triggers a render on all the list items when it's not something we would expect ?
Thank you for reading me :)
CodePudding user response:
Ok i finally figured it out.
After reading this article:
https://codeburst.io/5-vue-performance-tips-98e184338439
..that links to this github answer: https://github.com/vuejs/core/issues/3271#issuecomment-782791715
Basically, when using a component on a v-for, Vue needs to know when it has to update it.
To achieve that it looks for any prop used on the DOM and builds up a cache to make further updates faster.
But when using a prop on an event handler, Vue cannot build that cache.
If that prop is updated, Vue will know it is linked to your component but it won't know if it actually should trigger a render or not. It will trigger it just in case.
If you use a prop on every instances of the list, any update of that prop will trigger a render on all the items.
What's unclear to me though is why this does not happen if I simply remove this line from the example of my first post:
<div v-if="hoveredItem">hovered!</div>
The hoverItem
is still used on my event handlers so it should still trigger a render.
TLDR; don't use any property/var within a component event handler
(disclaimer: i may not have understood things properly, appologies if I'm wrong on some points)
CodePudding user response:
Your :key="item.id"
should be on the <TestComponent>
, it does not work on <template>
<template>
<template v-for="item in list" :key="item.id">
<TestComponent @mouseenter="hoveredItem = item" @mouseleave="hoveredItem = null" />
</template>
<div v-if="hoveredItem">hovered</div>
</template>
I also recommend you do get a linter so it will show your such errors