Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue v-for performance is poor

I have about 4000 objects being returned via AJAX. I'm looping over them with v-for and spitting them out into a table.

The initial load and render is very fast but I've also got an input field I'm using for 'instant search'. I use a computed property to filter the dataset using the input value and on a small dataset, say up to about 100 results this works superbly but as the dataset gets larger it gets a lot slower.

I'm rendering a table with 4 values, one of which is a custom Component. Removing the Component speeds things up but I'm surprised that it's this bad a performance hit. I'm not sure if there's something I'm missing or if someone could point me in the right direction?

I know its a large amount of data for one page but I thought this was what Vue was supposed to be good at. I googled the issue and for instance I found this codepen rendering a similar list of items and filtering in exactly the same way and I could copy-paste the number of items in the array all the way up to 10,000 or so and there was no perceptible performance hit when searching.

Steps I've taken to speed things up, these have made either tiny or no improvements:

  • Added a v-bind:key with a unique value on the v-for
  • Not using the table element and instead using div or ul
  • Forgoing the nativeJS .filter method because it can be slow, and using my own filter method.
  • Trying running it on a fresh codebase with only the dependencies that are needed to run.
  • And I am aware of pagination techniques etc. but I'm unwilling to do that unless I've exhausted all other possibilities.

Thanks

It wants me to paste code here, even though I've linked to codepen so here's the JS without the items array.

    Vue.component('my-component', {
  template: '#generic-picker',
  props:['items','query','selected'],
  created: function(){
    this.query='';
    this.selected='';
  },
  computed:{
    filteredItems: function () {
      var query = this.query;
      return this.items.filter(function (item) {
        return item.toLowerCase().indexOf(query.toLowerCase()) !== -1})
    }
  },
  methods:{
    select:function(selection){
      this.selected = selection;
    }
  }
})
// create a root instance
var genericpicker = new Vue({
  el: '#example'
});
like image 268
peos Avatar asked May 11 '17 10:05

peos


2 Answers

The problem with using a computed array is that things have to be un-rendered and re-rendered as if you're using v-if, when you're in a situation where v-show is a better choice.

Instead, keep an indicator for each item for whether it should be displayed, and use v-show based on that. The snippet below implements both, selectable by checkbox. You will find that filter updates are a bit halting when not using the v-show version, but keep up quite well when using v-show.

Most noticeable when you filter it down to 0 rows (say, filter on x) then show everything (remove the filter), but you can see a difference in partial filtering like me 2

let arr = [];
for (let i=0; i<6000; ++i) {
  arr.push({name: `Name ${i}`, thingy: `Thingy ${i}`});
}

Vue.component('tableRow', {
      template: '<tr><td>{{name}}</td><td>{{thingy}}</td></tr>',
      props: ['name', 'thingy']
    }
);

new Vue({
  el: '#app',
  data: {
    arr,
    filter: 'x',
    useVshow: false
  },
  computed: {
    filteredArr() {
      return this.filter ? this.arr.filter((item) => item.name.indexOf(this.filter) > -1) : this.arr;
    }
  },
  watch: {
    filter() {
      for (const i of this.arr) {
        i.show = this.filter ? i.name.indexOf(this.filter) > -1 : true;
      }
    }
  }
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<div id="app">
  Filter: <input v-model="filter">
  Use v-show: <input type="checkbox" v-model="useVshow">
  <table>
    <tr>
    <th>Name</th>
    <th>Thingy</th>
    </tr>
    <template v-if="useVshow">
    <tr is="tableRow" v-for="row in arr" v-show="row.show" :key="row.name" :name="row.name" :thingy="row.thingy"></tr>
    </template>
    <template v-else>
    <tr is="tableRow" v-for="row in filteredArr" v-show="row.show" :key="row.name" :name="row.name" :thingy="row.thingy"></tr>
    </template>
  </table>
</div>
like image 103
Roy J Avatar answered Nov 14 '22 17:11

Roy J


If you're not interested in two-way and/or reactive binding, that is, if you only want to visualize the objects rather than being able to edit them or update the view when the data changes, you can speed up performance dramatically with Object.freeze. This way, Vue.js can't add watchers on every property and instead only read the properties.

like image 6
wensveen Avatar answered Nov 14 '22 18:11

wensveen