Skip to content

Instantly share code, notes, and snippets.

@MasterGroosha
Created April 16, 2021 13:42
Show Gist options
  • Save MasterGroosha/627cc84960814b7396f45862868c3c26 to your computer and use it in GitHub Desktop.
Save MasterGroosha/627cc84960814b7396f45862868c3c26 to your computer and use it in GitHub Desktop.
Vue rendering: preventing unnecesary re-rendering by passing objects
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</script>
</head>
<body>
<div id="app">
</div>
</body>
</html>
const grandChildB = {
name: 'GrandChildB',
props: ['value'],
template: `
<div class="component-tree-b">
<p>
This is grand child B. This one is the only one that uses
the message. Because of passing values inside objects,
it is the only one with a dependency to the 'messageB'
field value and will be the only one to get re-rendered.
We emit the event 'update:messageB' to parents so that the
values get updated at the top level
</p>
<p>
Edit the message here: <input type="text"
:value="value.messageB"
@input="$emit('input',$event.target.value)"/>
</p>
<p>
You'll notice every key press only causes this component to
be re-rendered.
</p>
<p>
The message is this: {{ value.messageB }}
</p>
</div>`
}
const childB = {
name: 'ChildB',
props: ['value'],
components: {
grandChildB
},
template: `
<div class="component-tree-b">
<p>
This is child B. This one doesn't use the message either and
also just passes the whole settings object down to the
grandchild who is the one that will emit the events to
modify
it, and it will pass up events of 'update:messageB' to the
parent so it gets updated. As it has no reference to
'messageB' either, it will not get re-rendered.
</p>
<grand-child-b :value="value"
@input="$emit('update:messageB', $event)"></grand-child-b>
</div>`
}
const componentB = {
name: 'ComponentB',
components: {
childB
},
data() {
return {
settings: {
messageB: ''
}
};
},
template: `
<div id="component-b" class="component-tree-b">
<h1>The RIGHT way to do it</h1>
<p>
This is component B. We pass the message encapsulated inside
a 'settings' object instead of directly to children to
avoid having a reference to 'messageB' in this template.
And we listen to events from the children to update
it's value. We can't use v-model because v-model would
update the settings object, not the specific field that our
children want to update only. We follow Vue's convention
of using 'update:fieldNameHere' events when updating a
sub-field of the passed in prop instead of input (input
is normally used to update the entire value of the prop
that was passed in with v-model).
</p>
<p>
As a result of not having any references to 'messageB' in
this component, it doesn't get re-rendered any time the
children emit events to update the value of 'messageB'
</p>
<child-b :value="settings"
@update:messageB="settings.messageB = $event"></child-b>
</div>
`
}
const grandChildA = {
name: 'GrandChildA',
props: ['value'],
template: `
<div class="component-tree-a">
<p>
This is grand child A. This one is the only one that uses
the message so it makes sense for it to get re-rendered when
the messageA changes, but not the others
</p>
<p>
Edit the message here: <input type="text" :value="value"
@input="$emit('input',$event.target.value)"/>
</p>
<p>
You'll notice every key press causes all three components to
re-render. A hook is connected to the updated
event of the root Vue instance to catch and log any time a
component re-renders.
</p>
<p>
The message is this: {{ value }}
</p>
</div>`
}
const childA = {
name: 'ChildA',
props: ['value'],
components: {
grandChildA
},
template: `
<div class="component-tree-a">
<p>
This is child A. This one doesn't use the message either but
will also get re-rendered just for passing the prop down to
the
children
</p>
<grand-child-a :value="value"
@input="$emit('input', $event)"></grand-child-a>
</div>`
}
const componentA = {
name: 'ComponentA',
components: {
childA
},
data() {
return {
messageA: ''
};
},
template: `
<div id="component-a" class="component-tree-a">
<h1>The WRONG way to do it</h1>
<p>
This is component A. Due to passing direct fields as props
to children, you'll notice this component gets re-rendered
every time the 'messageA' changes due to child events, even
though this component does not use 'messageA'
</p>
<child-a v-model="messageA"></child-a>
</div>
`
}
const app = {
name: 'App',
components: {
componentA,
componentB
},
created() {
Vue.mixin({
updated() {
this.$root.$emit('on-re-rendering', this.$options.name)
},
})
this.$root.$on('on-re-rendering', (component) => {
this.logs.unshift({
componentName: component,
timestamp: new Date().toISOString()
})
})
},
template: `
<div>
<div>
<component-a></component-a>
<component-b></component-b>
</div>
<div id="logs-container">
<input type="button" @click="logs = []" value="Clear logs"/>
<ul>
<li v-for="(log) in logs" :key="log.timestamp">
{{ log.timestamp }}: Re-rendering component {{ log.componentName }}
</li>
</ul>
</div>
</div>
`,
data() {
return {
logs: []
};
},
};
new Vue({
render: h => h(app)
}).$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
html, body {
margin: 0;
padding: 0;
}
.component-tree-a, .component-tree-b {
border: 1px solid gray;
padding: 10px;
}
#component-a, #component-b {
display: inline-block;
margin: 0 5px;
font-family: sans-serif;
font-size: 16px;
line-height: 22px;
vertical-align: top;
}
#component-a {
width: 30%;
}
#component-b {
width: 60%;
}
h1 {
font-size: 40px;
line-height: 40px;
}
.component-tree-a {
border-color: indianred;
}
.component-tree-b {
border-color: cornflowerblue;
}
#logs-container {
padding: 20px 0 0 20px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment