-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
fix(runtime-core): ensure custom events are not emitted anymore after unmount. (fix #5674) #5679
Conversation
✅ Deploy Preview for vuejs-coverage ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
✅ Deploy Preview for vue-sfc-playground ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
✅ Deploy Preview for vue-next-template-explorer ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
Apologies if I am misunderstanding this change, @LinusBorg, but at a glance it doesn't look like it entirely fixes the problem I reported. It looks like this prevents a component from emitting events after it's been unmounted. But what about unmounted components handling events? Shouldn't event listeners be removed between the I've updated one of the StackBlitz snippets that I provided to show another example of the issue. I added a custom element ( https://stackblitz.com/edit/vue-kurpoq?file=src%2Fcomponents%2FAlertList.vue To add some context, the issue that we're experiencing is with a UI library that dispatches events asynchronously after animations complete. We also use Vue Router, and sometimes our users navigate away from a page in the middle of an animation. When that happens, we're seeing event handlers run in unmounted components, and that causes crashes if those handlers depend on injected dependencies, or |
Thanks for checking this PR! :)
That should not be an issue as child components are unmounted before the parent.
Event listeners are part of the vnode props. to remove them cleanly, we would have to run another patch run (re-ender), or remove them manually which feels icky. Preventing events from being emitted should have the same effect and be a bit cleaner.
Thanks for this, good catch! This is because I forgot to apply the fix to that part of the codebase as well, will fix that! |
Thanks, @LinusBorg, for helping with this issue.
I'll take your word for it on this one--I don't know enough about the Vue code base. I'll throw it out there, and sorry if I'm being naive, that the custom element I made was defined using the native Here's another example that illustrates the problem without using a custom element. Click "Toggle Menu" within 5 seconds and not that the event handler in |
It seems I wasn't reading your example carefully enough, my apologies. Coincidentally, my point is still something I need to address in this PR, so still: Thanks for the (involuntary) pointer.
What you are no asking here, basically, is that when unmounting, Vue should walk the element tree and remove all event listeners one by one, even though the elements would normally be garbage-collected anyway, so that you don't have to clean up the async side-effect you created yourself. (Of course a full tree walk would not strictly be necessary, it could be optimized, but it would come at a cost in terms of implementation and additional work at runtime one way or another). You created a timeout that you fail to cancel when your component unmounts. That should be the correct way to solve the problem. Us removing all the individual listeners would only solve this specific issue with this more general issue: Side-effects, introduced by the developer, that leak out of the component lifecycle. Instead of an element event listener as in your example, it could be a fetch request, or a DOM selector query, or any of a million other things happening in that timeout callback that will introduce bugs if you let the timeout run beyond the lifecycle of your component it is meant to work inside of. Here's two ways to write your example in a proper way:
mounted() {
const menu = document.querySelector('.removable-menu');
this.id = setTimeout(() => menu.dispatchEvent(new Event('delayed')), 5000);
},
beforeUnmount() {
this.id && clearTimout(this.id)
}
mounted() {
const menu = document.querySelector('.removable-menu');
this.id = setTimeout(() => {
if (menu.isConnected) {
menu.dispatchEvent(new Event('delayed')), 5000);
}
}
}, So in short: Yes, removing all event listeners one by one during unmount would provide a little safeguard in very specific scenarios, but is not worth the perf cost IMHO. Whereas the little safeguard concerning component events that I propose in this PR, is practically free. I understand that your underlying issue is specific to one component (-library). We can talk about what to do about the specific problem on discord (chat.vuejs.org) if you want. This PR would not be the place for that. |
Fair enough. We've already worked around the issue in our code by manually adding and removing event listeners in At any rate, thanks again for the help. The change you made in this PR does fix some of the issues that we're experiencing in our codebase. |
@LinusBorg
I'm not sure what to do. Do I not use emit and just use a "function" prop? Unfortunately I haven't been able to consistently reproduce (thus no link to provide yet). I just have to play around with my app until the back button stops working. UPDATE: I patched my vue install to remove the return and just log a message. Now I see
And everything works properly. |
I fail to imagine a scenario where the inner component would need to emit a close event, while its parent (the middle component) is already (being) unmounted - qnd yet somehow your page gets stuck. so I'll wait for your reproduction When you have that ready, please open a new issue, thanks. |
@LinusBorg I'll try to yank all this out of my app and create a reproducible snippet. In the meantime, what it the harm of me patching my vue install to remove those added lines? |
Not much, i guess. This patch covered mote of an edge case I'd say from what i recall. |
@LinusBorg I've create issue #7355 |
This change ensures that calling
emit
after the component has unmounted will no longer call any of the registered custom event listeners.Considerations
Is it possible/realistic that we have implementations out there that rely on the current behavior where this event would still be triggered?
If so, how do we approach this?
close: #5674