-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ability to disable reactivity to improve performance on destroy #3500
Comments
This is a really nice analysis of the existing problem. Thank you for that! |
Thank you @posva! We actually did the performance tests using a custom router link, as you suggested. However, there are two points I don't feel 100% comfortable with this solution:
On the whole, a custom router link is harder to maintain, and you probably end up picking desired features from the original source with the risk of doing something wrong on the way. This is a point for including a flag in the RouterLink itself though it could not be justified if it doesn't affect many projects. Regarding the impact, I think it might affect more projects than expected because the issue is not easy to detect and so report. We detected it because we were analyzing all Dep with a |
This just causes a leaky abstraction then. A feature which isn't frequently necessary, which is wrapped, and then wrapped, etc... Will force all the wrappers to implement similar functionality. A better solution here would be for the NuxtLink to accept a
Perhaps that's a better solution, perhaps there is a change that
It would be good to understand the feasibility to make a change to the |
I don't think this can change within RouterLink without major code complexity. Which isn't worth for an edge case. I rather add a new component and document it. Duplicating Note you can also use |
The main purpose of NuxtLink is prefetching the Components required for the new route, so the JS is already fetched if the user clicks on the link. They extend the RouterLink and read the As they extend the RouterLink, they don't have to expose anything from RouterLink; the interface is exactly the same but extended with the Nuxt functionality.
I don't see why it isn't discoverable. Maybe, I didn't explain correctly. By flag, I meant a prop like exact-path that should be documented explaining the use case and the side effects (disables active classes).
The root cause is the dependency that
I see what you mean. In my opinion, the current change is quite simple, but the complexity will grow if the new features to come depend on the Having two components to maintain has pros and cons. The main benefit is the All in all, I don't see the point of having the two components in the official vue-router for an edge case. I'd rather go with the
I tried it with no luck. It seems that the v-once creates the component but it doesn't destroy it, so watchers aren't removed until the component is removed from the DOM. Many thanks for the support! I hope I hadn't missed any point. |
Let me try this differently, how do I as a developer using vue-router know that I should add this flag. From the expectation of functionality, it doesn't seem like something anyone would intentionally set until they had a problem. And in that case, how do they know that the problem could be solved by adding the flag. For good usability, we should have an answer to that. Additionally, I think it is bad form to explicitly disable the functionality and thus break it for supporting applying the css changes. I'm not sure what the long term goal is, but basically it would be better to ask, does it make sense that it does this? If the answer is yes, then adding a property to disable this functionality, seems like the wrong approach. If the answer is: well, no, it likely shouldn't be doing this actually, then the change in the interim while the functionality is being removed is a good short term solution. So knowing the long term plans is critical to making that judgment.
This actually seems where the bug is more than anything. Why does removing a single RouterLink's Watcher cost |
@wparad, thanks for the clarification! I see what you mean and I think you're right. Change Use browser's native API. The point of watching the I suppose the first option isn't worth it because it's at the heart of the Vue 2 dependency system and it's already fixed on Vue 3. The second change would help projects still using Vue 2 although, if it's already fixed on Vue 3, does it make sense to change the RouterLink implementation now?
The main point is that the cost of removing a Watcher from a Dependency ( |
Hello all, I will close the issue since Vue 3 seems to have solved the problem internally because they have replaced the subscribers array with a set, and the set has constant performance for deletion. I don't have time to test it now, but it should really help. Here it is a very similar problem in vue-i18n and the performance after applying this little change. In their case, they don't use Vue internals to track the reactivity on language changes, but the underlying problem was exactly the same. |
Posted a bounty here: https://app.bountysource.com/issues/97354830-ability-to-disable-reactivity-to-improve-performance-on-destroy |
What problem does this feature solve?
Destroying N RouterLink at once has a cost of
O((N^2)/2)
due to all RouterLink depend on the singleton reactive property$route
. When removing hundreds or thousands of links from the page, this cost is noticeable, especially on slow devices. The goal is a cost ofO(N)
.Hundreds or thousands of links might seem too much, but sure there might be some cases where it makes sense, like an infinite scroll or some big grids. In many cases vue-virtual-scroller should solve the general problem, but I think it would be better not to require it as a rule.
Removing the dependency on
$route
means effectively disabling reactivity for the RouterLink and thus all the related features. At this moment, I think the only affected features are the CSS classes to show a link is active.Why this cost?
The singleton
_route
is created for the_routerRoot
once. At this moment, Vue instantiates a Dep to track writes and reads on this attribute.Each time a RouterLink is created, its render Watcher detects the
$route
is accessed, so that the Watcher adds the Dep to its dependencies (Watcher.deps
) and the Dep adds the Watcher to its subscribers (Dep.subs
). This means theDep.subs
for_route
is, at least, as large as the number of rendered RouterLinks.When the
$route
changes, all the RouterLink are updated because they are on theDep.subs
.When destroying all the RouterLink (page change), all the components teardown their watchers, and each watcher have to remove itself from the
Dep.subs
. Removing a single RouterLink's Watcher from theDep.subs
costs O(N - i), whereN
is the number of RouterLink andi
the number of RouterLink already removed.Generally speaking, the cost of removing N RouterLinks at once is O((N^2)/2).
What does the proposed API look like?
Adding a
no-reactive
prop on RouterLink should be enough.The meaning of this prop is effectively disabling reactivity on the current route to improve the overall performance, especially when destroying many RouterLink at once.
As a side effect, this flag turns off any feature that depends on the current route. At this moment, I think the only affected features are related to the CSS classes to show if a link is active or not.
The text was updated successfully, but these errors were encountered: