Creating a custom component results in an extended duration to 'eliminate' its children

1I am currently facing an issue with a time-table component created using vue.js. It contains approximately 200 nested child timeline components, making it quite complex (I wanted to share an image but lacked the reputation to do so).

The main problem lies in the fact that this component takes more than 6 seconds to be destroyed.

According to Chrome, the 'remove' function (called by vue.js when a component is being destroyed) is being called multiple times, each taking around 20-40ms.

The vue.js remove function looks like this:

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

It appears that the first argument, 'arr,' consists of either a few VueComponents or over 2000 Watcher objects.

My questions are as follows: 1. What exactly is a 'Watcher' in this context and why does it exceed 2000 in number? 2. Why does it take such a long time to destroy the components despite not handling an excessive amount?

I suspect that these issues may be related to the specifications of vue.js. If anyone has encountered similar problems or has insights on this matter, I would greatly appreciate your help. Thank you!

The timeline component is displayed above, with gray-background panels and purple background panels (with a man icon) representing the child components. When a purple panel is clicked, vue-router navigates to the details page, resulting in the destruction of all components (which triggers the aforementioned issues)

Answer №1

Our team has encountered similar issues and identified a common root cause: an abundance of components relying on the same reactive object. There are three main scenarios that can impact any project:

  • Having numerous router-link components
  • Utilizing many components (of any type) with Vue I18n installed
  • Using multiple components that directly access the Vuex store in their render or computed properties.

Our recommended approach involves refraining from accessing shared reactive objects within the render and computed properties functions. Instead, consider passing them as props (reactive) or accessing them in the created or updated hooks (non-reactive) to store them in the component's $data. Further details for each of the three cases are provided below.

An Overview of Vue 2 Reactivity Mechanics

(Please skip this section if not needed)

The reactivity mechanism in Vue revolves around two interconnected objects: Watcher and Dep. Watchers maintain a list of dependencies (Deps) in the deps attribute, while Deps include a list of dependents (Watchers) in the subs attribute.

For every reactive element, Vue creates a corresponding Dep entity to track reads and writes associated with it.

A Watcher is established for each component (specifically, for the render function) and Computed Property. These Watchers essentially oversee the execution of a function. During this oversight, if a reactive object is accessed, the linked Dep is alerted, establishing a relationship between them: The Watcher.deps includes the Dep, and the Dep.subs contains the Watcher.

Subsequently, upon modification of the reactive element, the relevant Dep notifies all its dependents (Dep.subs) instructing them to update (Watcher.update).

Upon destruction of a component, its associated Watchers are also dismantled. This process entails scanning through each Watcher.deps to eliminate the Watcher itself from the Dep.subs (refer to Watcher.teardown).

The Issue at Hand

All components dependent on the same reactive entity introduce a Watcher onto the identical Dep.subs. For instance, in a scenario where the same Dep.subs comprises 10,000 watchers:

  • Rendering 1,000 items (e.g., grid layout, infinite scroll, etc.)
  • Each item involving 10 components: itself, 2 router-links, 3 buttons, and 4 other elements (nested and non-nested, spanning own codebase or third-party tools).
  • All components reliant on the same reactive object.

When terminating the page, the 10,000 watchers gradually detach themselves from the Dep.subs array individually. The cost incurred during removal is calculated as 10k * O(10k - i), with 'i' denoting the count of already-removed watchers.

In general, the expense of removing n items follows an O((n^2)/2) scheme.

Possible Solutions

If your project involves rendering myriad components, strive to circumvent accessing shared reactive dependencies within the render or computed properties.

Instead, contemplate conveying them via props or retrieving them in the created or updated hooks and storing them in the component's $data section. It's important to note that these hooks aren't monitored, meaning the component won't refresh in response to alterations in the data source – a suitable option for static data scenarios (wherein data remains unchanged post-component mount).

In situations featuring a lengthy list of rendered items, leveraging vue-virtual-scroller can be beneficial. In such instances, you can still interact with shared reactive dependencies since vue-virtual-scroller utilizes a small collection of components (ignoring off-screen renditions).

Keep in mind that dealing with thousands of components may not be as challenging as anticipated, primarily due to our inclination towards crafting petite components and assembling them (which is indeed commendable practice).

Scenario: Vuex

If your operation within the render or computed property mimics the example below, your component becomes entwined with the entire chain of reactive entities: state, account, profile.

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

In this context, assuming the account remains unaltered post-mounting, extract the value in the created or beforeMount hook and retain the name in the Vue $data. Since this falls outside the purview of the render function or computed property, no Watcher continuously supervises the store access.

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

Scenario: router-link

Refer to the issue #3500

Scenario: Vue I18n

While sharing a fundamentally similar core predicament, the Vue I18n case presents a slightly distinct explanation. Explore issue #926 for further insights.

Answer №2

Don't blame Vue for this issue, take a look at your mixins and options.
For example, using i18n (which causes headaches) in 200 components will yield the same outcome. This results in removing numerous watchers on beforeDestroy, causing a significant improvement in performance. Without i18n, the list functions a whopping 30 times faster.
How can you resolve this? Simply relocate the sluggish hook-handlers to the parent component and acquire essential data/methods from there.

A scenario involving i18n

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

Implementation:

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is causing the table to not be displayed in this Javascript program when it is used in a

I am currently experimenting with incorporating an infinite loop before the prodNum and quantity prompts to consistently accept user input and display the output in a table. Although the program is functional when executed, it fails to showcase the table ...

I possess a pair of UI tabs. Whenever a button located outside the tab is clicked, I am required to activate a distinct tab

As a beginner in Javascript, I recently encountered an issue with a UI tab element that contains two tabs. My goal is to create a button that, when clicked, will scroll up and activate the second tab. <style> .tab-container { overflow-x: ...

The action of POSTing to the api/signup endpoint is

Currently delving into the MEAN stack, I have successfully created a signup api. However, when testing it using POSTMAN, I encountered an unexpected error stating that it cannot POST to api/signup. Here is a snapshot of the error: Error Screenshot This ...

Analyzing and tallying JSON attributes using JavaScript

I have a JSON object with items that I need to analyze in JavaScript. When I view the JSON in the console, there is an element called items that contains an array of relevant information. console.log(json) {current_page: 1, per_page: 100, total_entries: ...

What steps can I take to make sure that a sub component's props are refreshed properly?

I'm encountering an issue with RTK queries in my project. I have a parent component that contains a table component. When a refresh event occurs, such as deleting data, the parent component receives updated data and passes it down to the child compone ...

Why is it that methods lose their binding when they are returned from a ternary operator?

class TestClass { constructor() { this.prop = 5; } MethA() { console.log(this); console.log(this.prop); } MethB() { (true ? this.MethA : null)(); } } Test = new TestClass(); Test.MethB(); What is the ...

Using jQuery to choose options and change the background color

Hey there, I have a select box below: <select id="events"> <option value="1" style="background-color:green;">Event 1</option> <option value="2" style="background-color:yellow;">Event 2</option> <option value="3 ...

Setting the maxDate property for the datepicker in element.io using Vue.jsBelow are instructions on how to set

I am currently utilizing the ''. I have integrated 2 date pickers from this component. Let's refer to them as Picker A and Picker B. My goal is to set the max-Date property for Picker A. If I select a date from Picker B first, then that ch ...

Incorporating the non-typescript npm package "pondjs" into Meteor applications using typescript files

Implementing the Pondjs library into my project seemed straightforward at first: meteor npm install --save pondjs However, I'm encountering difficulties when trying to integrate it with my Typescript files. The documentation suggests: In order ...

"Exploring the World Wide Web with Internet Explorer Driver and Webdriver

After trying everything I know to make internet explorer work with webdriver.io, I've encountered a confusing issue. To start, download the internet explorer driver from this link: http://www.seleniumhq.org/download/. The file is an .exe named ' ...

Organizing a Vue.js SPA project: Implementing Vuex store and API calls efficiently

Here is how I have organized the structure of my Vue app: components/ article/ AppList.vue common/ AppObserver.vue NoSSR.vue layout/ AppFooter.vue AppHeader.vue ui/ AppButton. ...

How can I create an event that is specific to only one element in Vue.js?

I am currently facing an issue with Vue events attached to looped elements. My challenge lies in displaying CRUD actions on individual items. Currently, all looped items display their own set of CRUD actions. Is there a way to make these actions unique t ...

It is not possible in Vue.js to alter a data value from within a method

I've been struggling to figure out why the data value within a method won't change. Can someone help me with this issue? <head> <script src="https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" dat ...

Having issues with importing images in Next.js using the Next Images package

Having trouble with importing images locally from the images folder. Error message: "Module not found: Can't resolve '../images/banner1.jpg'" https://i.stack.imgur.com/Dv90J.png Attempting to access images in ImagesSlider.js file at compo ...

Troubleshooting Next.js and NextAuth.js Authentication Redirect Issue

I am experiencing a problem with authentication in my Next.js application using NextAuth.js. The issue specifically pertains to the redirection after successful login. Here is an overview of my setup: NextAuth.js Configuration (app/api/auth/[...nextauth.js ...

I'm encountering the error message "Controller not a function, received undefined" even though I have not declared the controller globally

Encountering the same ERROR Argument 'AveragesCtrl' is not a function, got undefined. Despite attempting various solutions found on SO, I am still unable to resolve this issue. Any insights into what might be causing my error? <div > ...

Tips for concealing a particular button that shares the same class designation

Is there a way to create a function in vanilla JavaScript that can hide a specific button? <button class"btn">button 1 </button> <button class"btn">button 2 </button> <button class"btn">button 3 </button> Specifically, ...

The react-datepicker component is unable to set the state to the format dd/MM/yy

The date is currently shown in the correct format (31/08/21), but when onChange gets triggered, startDate changes to something like this: Tue Aug 31 2021 21:29:17 GMT+0200 (Central European Summer Time) Is there a way to maintain the display format I wa ...

Ways to resolve: The JSX component does not contain any construction or call signatures

I've been grappling with a persistent issue regarding the creation of custom elements dynamically in React TypeScript. If you're curious, you can check out the question here. const generalButtons: MenuButton[] = [ { text: "New Cl ...

What is the best way to incorporate auto refresh in a client-side application using vue.js?

Disclaimer: I have separated my client application (Vue.js) from the server side (DjangoRest). I am utilizing JWT for validating each request sent from the client to the server. Here is how it works - The client forwards user credentials to the server, an ...