Component re-renders each time after changing the state if the event is attached to the component
Version
3.0.5
Reproduction link
https://github.com/sanjade/vue3-bug
Steps to reproduce
git clone https://github.com/sanjade/vue3-bug
cd vue3-bug && yarn install
yarn dev
Click `Add` button
See in console `<p>Navbar</p> onMounted()` getting rebuilt each time you click `Add` button
What is expected?
So I have a component named ComponentRenderer.js
which basically accepts html via a prop and renders it. And in html supplied to this component in App.vue
, it has a for loop which uses count from vuex.
The html also includes the same component <component-renderer>
which renders <p>Navbar</p>
and also has onClick
event attached to it. Now, whenever the count is increased via the button, the navbar gets rebuild each time.
This happens only if onClick
event is attached to the component. If I remove onClick
event then the navbar doesn't gets rebuild each time.
I expect navbar to not get rebuild everytime the count is increased.
What is actually happening?
Navbar keeps on rebuilding everytime the count is increased.
Your reproduction doesnt do what it describes. It has a store but doesn't use them for any kind of loop.
Please provide a complete reproduction that actually demonstrates the problem.
@LinusBorg If you goto src/App.vue
, there's this html in setup function:
const html = `
<component-renderer :html="'<p>Navbar</p>'" @click="log('Test')"></component-renderer>
<p>BODY</p>
<button @click="$store.state.count++">Add</button>
<div v-for="a in $store.state.count">{{ a }}</div>
`
As you can see there's a for loop in that html. Now, I supply this html to component-renderer
component for Vue to compile html to Vue template. Now, click Add
in the webpage and you can see <component-renderer :html="'<p>Navbar</p>'" @click="log('Test')"></component-renderer>
component rebuilds everytime the count is increased. See the console log This only happens because of @click="log('Test')"
in the html. If I remove this event the the navbar doesn't get rebuilt.
I think two problems, combined, led to this effect:
@click="log('Test')"
generates an inline function: () => log('Test')
, which make the inner component update, technically, the passed listener changed. The SFC compiler can cache these kinds of inline function, but the runtime compiler that you are using, can't.
Also, you create the component object in render()
, which means whenever the inner component-renderer
updates (because of the listener), it will create a new, different component, which will have to be mounted.
Solutions:
- create the component once, in
setup
orcreated
. That will keep the component from being re-created - Avoid the generation of an inline listener function by passing a function without arguments:
@click="logTest"
function logTest () {
log('Test')
}
The first one is the important fix, the second one an optimization.