Suppose I have an input field in Vue.JS that v-model
bind to a String data property, and a long list of random numbers that are completely unrelated to that first String.
data: {
input: "",
randoms: []
}
<input type="text" v-model="input">
<p v-for="random in randoms" v-text="random"></p>
When I put both in the same Vue, I see a huge slowdown when typing in the input field, as it appears Vue is reevaluating the DOM for each list entry after every input event, although they really have nothing to do with each other.
https://jsfiddle.net/5jf3fmb8/2/
When I however move the v-for
to a child component where I bind randoms
to a prop, I experience no such slowdown
https://jsfiddle.net/j601cja8/1/
Is there a way I can achieve the performance of the second fiddle without using a child-component?
Is there a way I can achieve the performance of the second fiddle without using a child-component?
No.
Whenever any dependency of the template changes, Vue has to re-run the render function for the entire component and diff the new virtualDOM against the new one. It can't do this for this or that part of the template only, and skip the rest. Therefore, each time the input value changes, the entire virutalDOM is re-rendered.
Since your v-for is producing quite a bit of elements, this can take a few 100ms, enough to be noticable when you type.
Extracting the heavy part of the template into its own component is in fact the "official" way to optimize that.
As Alex explained, v-model.lazy might improve the situation a bit, but does not fix the core of the issue.
Shortest, simplest answer: change v-model
to v-model.lazy
.
When I put both in the same Vue, I see a huge slowdown when typing in the input field, as it appears Vue is reevaluating the DOM for each list entry after every input event, although they really have nothing to do with each other.
Note that the OnceFor sample still chugs like mad despite not actually being reactive any more. I don't understand Vue well enough to say if that's intentional or not.
const Example = {
data() { return { input: "", randoms: [] } },
created() { this.newRandoms() },
methods: {
newRandoms() { this.randoms = Array(50000).fill().map(() => Math.random()) }
}
}
new Vue({
el: "#vue-root",
data(){ return {example: 'lazy-model'}},
components: {
LazyModel: {...Example, template: "#lazy-model"
},
OnceFor: {...Example, template: "#once-for"
},
InlineTemplate: {...Example, template: "#inline-template",
components: {
Welp: {
props: ['randoms']
}
}
}
}
})
button,
input,
div {
margin: 2px;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="vue-root">
<span><button v-for="(component, name) in $options.components" @click="$set($data, 'example', name)">{{name}}</button></span>
<component :is="example"></component>
</div>
<template id="lazy-model">
<div>
<input type="text" v-model.lazy="input"><br>
<input type="submit" value="Regenerate" @click="newRandoms">
<p v-for="random of randoms" v-text="random"></p>
</div>
</template>
<template id="once-for">
<div>
<input type="text" v-model="input"><br>
<input type="submit" value="Regenerate" @click="newRandoms">
<p v-for="random of randoms" v-text="random" v-once></p>
</div>
</template>
<template id="inline-template">
<div>
<input type="text" v-model="input"><br>
<input type="submit" value="Regenerate" @click="newRandoms">
<welp :randoms="randoms" inline-template>
<div>
<p v-for="(random, index) of randoms" :key="index"> {{index}}: {{random}} </p>
</div>
</welp>
</div>
</template>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With