v-for: inline native event listeners force re-render of each list component when one item in the list changes.
Vue version
3.2.45
Link to minimal reproduction
Steps to reproduce
go to that link and click on the number There is only one object where the change occurs, but all components are re-rendered.
What is expected?
There is only one object where the change occurs, but all components are re-rendered.
What is actually happening?
There is only one object where the change occurs, but all components are re-rendered.
System Info
System:
OS: Windows 10 10.0.19044
CPU: (12) x64 AMD Ryzen 5 5600X 6-Core Processor
Memory: 8.41 GB / 15.93 GB
Binaries:
Node: 16.17.0 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.19 - ~\AppData\Roaming\npm\yarn.CMD
npm: 8.19.1 - C:\Program Files\nodejs\npm.CMD
Browsers:
Edge: Spartan (44.19041.1266.0)
Internet Explorer: 11.0.19041.1566
npmPackages:
vue: ^3.2.45 => 3.2.45
Any additional comments?
If you pass a non-age object directly as props, only the elements that change will be re-rendered. Why do all re-renders happen when age is passed as props instead of an object?
I don't quite understand your question, your description in expected
is the same as the description in actually happening
,What does all re-renders
mean?
Cause
The click handler on the child gets a new inline function on every parent re-render, causing every child to update:
const _item = (_openBlock(), _createBlock(Item, {
key: i,
isSpread: i.age,
onClick: $event => (ageUp(index)) // <-- this causes the re-render
}, null, 8 /* PROPS */, ["isSpread", "onClick"]))
_item.memo = _memo
Compiler seems to have problems creating cache entries for these handlers as they are unique to every child item - they use the index. Something for us to think about how to improve/fix.
Workaround/Improvement
Use v-memo to tell the for-loop which items need a re-render (Playground)
Cause
The click handler on the child gets a new inline function on every parent re-render, causing every child to update:
const _item = (_openBlock(), _createBlock(Item, { key: i, isSpread: i.age, onClick: $event => (ageUp(index)) // <-- this causes the re-render }, null, 8 /* PROPS */, ["isSpread", "onClick"])) _item.memo = _memo
Compiler seems to have problems creating cache entries for these handlers as they are unique to every child item - they use the index. Something for us to think about how to improve/fix.
Workaround/Improvement
Use v-memo to tell the for-loop which items need a re-render (Playground)
I noticed that when the compiler compiles this code, it treats onClick
as a dynamicProps
. When its value is an arrow function, the component considers the previous props and next props to always be different, causing it to repeatedly re-render.
In this case, I don't think that events which starts with on
should be considered as dynamicProps
to be checked for changes. Since it can be guaranteed that the value of @xxx
is always a function
, it seems that there are no scenarios where the event is updated.
Since it can be guaranteed that the value of @xxx is always a function, it seems that there are no scenarios where the event is updated.
It's not as easy as that, unfortunately.
It's always always a function, but it's always a new function, that could potentially do different things when being called (Vue can't be sure that the function's behavior is guaranteed to be stable across re-renders. Especially since that function could have been defined in a custom render function for all the renderer knows. But even from an SFC, things like this can happen:
<li v-for="(item, index) in items">
<my-component :item="item" @click="() => doSomething(index)" />
</li>
The inline function above holds a reference to the index
. if that index
changes, only the new function, created during re-render, holds a reference to that new index. So we need to re-render the child so it can do potentially necessary updates with that new function.
there sure are ways to improve this, but simply ignoring these changes in the patch phase is not a valid solution.
Edit: It's worth clarifying that this does not affect component events it only affects listeners who are not declared via defineEmits
/emits:
and are subsequently added as event-listeners to the component's root element.