Matt
Matt

Reputation: 113

Improving performance for many Vue components on the same page

I am building an app that mounts hundreds or even thousands of instances of a vue component in the same view. Each component has an id that refers to an object in the vuex store.

<my-component :id="item1"></my-component>
<my-component :id="item2"></my-component>
<my-component :id="item3"></my-component>
...

The store:

state: {
  myComponentData: [
    {
      id: 'item1',
      name: 'Hawaiian Shirt', 
      selected: false,
      // ...
    },
    // ...
  ]
}

In my-component, I look up its object in the store and store it in a computed property:

computed: {
  myComponentData: function () {
    return this.$store.state.myComponentData.find(el => el.id === this.id);
  },
  isSelected: function () {
    return this.myComponentData.selected;
  },
  // ...
}

The object properties in the store can be manipulated by interacting with the component itself or by other components elsewhere in the app.

The problem is when I try running this with several hundred or 1000+ elements, it takes 30 seconds or longer to load.

If I remove the computed properties, or replace them with static values, then the page loads seamlessly. It's only when I try to load all of the computed properties from the store that I have lag issues.

Is there a way to improve performance when loading many (1500+) Vue components that use computed properties and Vuex? I have looked into dynamic/async components, vue-async-computed, and vue-rx as possible solutions, but I'm not sure which would help in this case.

Upvotes: 3

Views: 3378

Answers (2)

Richard Matsen
Richard Matsen

Reputation: 23463

I think the Array.find() operation is most costly. Having it in the computed property means it runs every time the dependencies change.

You can get the array index in mounted() and use it in the computed,

export default {
  name: "MyComponent",
  props: ["id"],
  data() {
    return {
      storeIndex: -2
    };
  },
  mounted() {
    this.storeIndex = this.$store.state.myComponentData.findIndex(
      el => el.id == "item" + this.id
    );
  },
  computed: {
    myComponentData: function() {
      // return this.$store.state.myComponentData.find(
      //   el => el.id === "item" + this.id
      // );
      return this.$store.state.myComponentData[this.storeIndex] || {};
    },
    isSelected: function() {
      return this.myComponentData.selected;
    }
  }
};

Here is a CodeSandbox with a working example.

Upvotes: 1

Andrey Popov
Andrey Popov

Reputation: 7510

The problem is within the logic. If you have 1000 items, you create 1000 components. Then each of them loops through the list of items in order to find the proper one based on id. So you are basically doing 1000 * 1000 loops (worst case of course). Which is not the worst thing - you are adding a reactivity through this computed property! Every time something changes, the computed method will fire again, leading to another 1000 loop (worst case scenario if your item is the last).

You have a few options:

  1. Get the store's item while setting the data in your component. This will not create a computed property, so you'll be having a static data. Which will lead to making your own personal watchers for all the data you want changed.

  2. (I would go with this) You're having the list of items in the parent component that shows the list. Don't pass just the id - pass the whole item. This way you won't need to use any computed properties, and you will have reactivity out of the box (props gets updated in children automatically).

Just don't add that many loops and computed properties - as the name states, they are for computing, not for filtering something you can filter elsewhere ;)

Good luck!

Upvotes: 1

Related Questions