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)
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.
To make a component delete itself in Vue. js, we can use the this. $destroy method. to add the close method to call this.
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.
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) } } }
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.
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 ).
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.
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:
router-link
componentsOur 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.
(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).
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:
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)
.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With