Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue.js - Takes too long to 'destroy' components

1I have a time-table component that is created with vue.js and it includes around 200 child timeline components as nested form (I wanted to upload image but couldn't without 10 reputations).

The problem now is that it takes more than 6 seconds to destroy this component.

Chrome says that 'remove' function (,which is called by vue.js everytime we destroy a component,) is called many times, and each of them takes around 20 - 40ms.

The vue.js remove function is like below:

function remove (arr, item) {
  if (arr.length) {
    var index = arr.indexOf(item);
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}

and it seems that the first argument ,arr, is either a few VueComponents or more than 2000 Watcher objects.

Now, my questions are: 1. What is 'Watcher' in this context and why the number it exceeds 2000? 2. Why it takes such long time despite I do not handle like 10000 components or so?

I guess it is the matter of specification of vue.js, but please help me if you have a similar problem or have any idea about this matter. Thank you!

enter image description here Above is how the timeline component appears, and each of gray-background panels and purple background panel(with a man icon) are child components. When you click a purple panel, vue-router makes routing to the page of the detail, and at that time all of components are destroyed (that is when the problems above occurs)

like image 291
高田悠 Avatar asked Apr 25 '19 06:04

高田悠


People also ask

Is Vue slow Javascript?

Vue JS is a very fast and lightweight frontend framework by design, but there are certain coding decisions that you could be making that are slowing down your app. Remember that every second wasted on loading affects your user's experience.

How do I destroy a component in VueJS?

To make a component delete itself in Vue. js, we can use the this. $destroy method. to add the close method to call this.

How will you solve common causes of memory leaks in VUE JS applications?

Resolving the Memory Leak In the above example, we can use our hide() method to do some clean up and solve the memory leak prior to removing the select from the DOM. To accomplish this, we will keep a property in our Vue instance's data object and we will use the Choices API's destroy() method to perform the clean up.

How long does it take to remove a Vue component?

Chrome says that 'remove' function (,which is called by vue.js everytime we destroy a component,) is called many times, and each of them takes around 20 - 40ms. function remove (arr, item) { if (arr.length) { var index = arr.indexOf (item); if (index > -1) { return arr.splice (index, 1) } } }

Why does vuejs render the same elements more times than needed?

One of the nastiest issues you can run into with VueJS is rendering the same elements or list of elements more times than needed. To understand why or how this can happen we have to understand reactivity in Vue. This example is from the official Vue.js documentation and it shows which properties are reactive and which are not.

Why does vue-router delete all components at once?

When you click a purple panel, vue-router makes routing to the page of the detail, and at that time all of components are destroyed (that is when the problems above occurs) Your deletion time is normal for Vue (see benchmark vogloblinsky.github.io/web-components-benchmark ).

Why should I use Vue instead of HTML?

It's common for an app to be organized into a tree of nested components: This is very similar to how we nest native HTML elements, but Vue implements its own component model that allow us to encapsulate custom content and logic in each component. Vue also plays nicely with native Web Components.


1 Answers

We have experienced similar issues and found that all share the same underlying problem: too many components that depend on the same reactive object. These are the 3 main cases that may impact any project:

  • Many router-link components
  • Many components (any kind) when Vue I18n is installed
  • Many components that directly access the Vuex store on its render or computed properties.

Our approach is to avoid accessing shared reactive objects on the render and computed properties functions. Instead, pass them as props (reactive) or access them on the created or updated hooks (not reactive) to store in the component's $data. Read below for more details and each of the 3 cases.

A brief explanation of Vue 2 reactivity

(skip this if you don't need it)

The Vue reactivity basically relays on two intertwined objects: Watcher and Dep. Watchers have a list of dependencies (Deps) in the deps attribute, and Deps have a list of dependants (Watchers) in the subs attribute.

For every reactive thing, Vue instantiates a Dep that tracks reads and writes on it.

Vue instantiates a Watcher for every component (actually, for the render function) and every Computed Property. The Watchers watch a function during its execution. While watching, if a reactive object is read, the associated Dep notices the Watcher, and they become related: The Watcher.deps contains the Dep, and the Dep.subs contains the Watcher.

Afterwards, if the reactive thing changes, the associated Dep notifies all its dependants (Dep.subs) and tells them to update (Watcher.update).

When a component is destroyed, all its Watchers are destroyed as well. This process implies iterating each Watcher.deps to remove the Watcher itself from the Dep.subs (see Watcher.teardown).

The problem

All the components that depend on the same reactive thing insert a Watcher on the same Dep.subs. In the following example, the same Dep.subs contains 10,000 watchers:

  • 1,000 items rendered (e.g. a grid, an infinite scroll, ...)
  • Each item implies 10 components: itself, 2 router-link, 3 buttons, 4 other (nested and not nested, from your code or third party).
  • All components depend on the same reactive object.

When destroying the page, the 10,000 watchers will remove themselves from the Dep.subs array (one by one). The cost of removing themselves is 10k * O(10k - i) where i is the number of watchers already removed.

In general, the cost of removing n items is O((n^2)/2).

Workarounds

In case you render many components, avoid accessing shared reactive dependencies on the render or computed properties.

Instead, pass them as props or access them on the created or updated hooks and store them on the component's $data. Bear in mind that the hooks aren't watched so the component won't be updated if the source of data changes, which is still suitable for many cases (any case where the data won't change once the component is mounted).

If your page renders a long list of items, the vue-virtual-scroller is bound to help. In this case, you can still access shared reactive dependencies because the vue-virtual-scroller reuses a small pool of your components (it does not render what is not seen).

Take into account that having thousands of component might be easier than you expect because we tend to write small components and compose them (actually a good practice)

Case: Vuex

If you do something like this in your render o computed property, your component depends on all the chain of reactive things: state, account, profile.

function myComputedProperty() {
    this.$store.state.account.profile.name;
}

In this example, if your account does not change once the component is mounted, you can read it from the created or beforeMount hook and store the name on the Vue $data. As this is not part of the render function nor part of a computed property, there is no Watcher watching the access to the store.

function beforeMount() {
    this.$data.userName = this.$store.state.account.profile.name;
}

Case: router-link

See the issue #3500

Case: Vue I18n

This has the same underlying problem but with a bit different explanation. See the issue #926

like image 162
Emilio Avatar answered Sep 20 '22 17:09

Emilio