admin管理员组

文章数量:1207127

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!

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)

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!

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)

Share Improve this question edited May 10, 2024 at 13:35 Kalnode 11.3k3 gold badges40 silver badges73 bronze badges asked Apr 25, 2019 at 6:18 高田悠高田悠 3232 silver badges7 bronze badges 7
  • 2 Your deletion time is normal for Vue (see benchmark vogloblinsky.github.io/web-components-benchmark). The issue here might be that removal is done by removing item by item, instead of a subarray of items. You might want to show us more of your code, i.e. how you call function remove, what is watcher that you are talking about. Your current code sample is not enough to figure out anything. – blaz Commented Apr 25, 2019 at 7:11
  • Do you have destroy/beforeDestroy hooks in your timeline child components? I think the code of the timeline component can be helpful to understand your problem. – NaN Commented Apr 25, 2019 at 9:03
  • Sorry for being late, I guess showing codes to explain is a little complex to me because of many types of child components are included in the parent component. Instead, I added a screen capture and the description of how the component works. I can show you my code if you could tell me which part of the image is required. Thanks. – 高田悠 Commented May 7, 2019 at 4:03
  • Same issue here, on the same remove function. Did you find a solution? – Aurelien Maigret Commented Nov 8, 2019 at 10:35
  • @AurelienMaigret, this question is technically unanswerable as it does not have sufficient info to reproduce and/or debug the problem. If you need an answer to a similar one, I suggest you write up your own question which could allow others to reproduce and debug the issue. Ideally it should contain a minimal reproducible example. – tao Commented Sep 22, 2020 at 14:35
 |  Show 2 more comments

2 Answers 2

Reset to default 19

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

It's not a vue problem, see on your mixins/options.
Eg. i18n (my pain) in every of 200 components will show the same result. It removes a lot of watchers on beforeDestroy. Without i18n the list works 30 times faster.
How to fix it? Move the slow hook-handlers to the parent component and get needed data/methods from it.

The sample with i18n

Vue.mixin({
    beforeCreate() {
        if (this.$options.useParentLocalization) {
            this._i18n = parent.$i18n;
        }
    },
});

Usage:

new Vue({
  // i18n, <-- before
  useParentLocalization: true,
  components: {
    Component1
  }
})

本文标签: javascriptCustom component takes too long to 39destroy39 childrenStack Overflow