Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue.JS 2.0 slow when modifying unrelated data

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?

like image 751
Maximilian Schier Avatar asked Dec 08 '16 15:12

Maximilian Schier


2 Answers

Is there a way I can achieve the performance of the second fiddle without using a child-component?

Short answer

No.

Long answer

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.

like image 107
Linus Borg Avatar answered Nov 08 '22 22:11

Linus Borg


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>
like image 29
Alex Sakharov Avatar answered Nov 08 '22 22:11

Alex Sakharov