Closed cnweibo closed 8 years ago
Because it is general proposal discussion, i have not created fiddle yet. If it is required, I'd like to create to demonstrate. thanks~!
This has already been justified on the 2.0 Changes
Here's a copy:
How to Deal with Deprecation of
$dispatch
and$broadcast
?The reason that we are deprecating
$dispatch
and$broadcast
is that event flows that depend on the components tree structure can be hard to reason about when the components tree becomes large (simply put: it doesn't scale well in large apps and we don't want to set you up for pain later).$dispatch
and$broadcast
also do not solve the communication between sibling components. Instead, you can use a pattern similar to the EventEmitter in Node.js: a centralized event hub that allows components to communicate, no matter where they are in the components tree. Because Vue instances implement the event emitter interface, you can actually use an empty Vue instance for that purpose:var bus = new Vue()
// in component A's method bus.$emit('id-selected', 1)
// in component B's created hook bus.$on('id-selected', function (id) { // ... })
This pattern can serve as a replacement for
$dispatch
and$broadcast
in simple scenarios. But for more complex cases, it is recommended to introduce a dedicated state management layer using Vuex.
The example shown on the upgrading guide is the same as you're talking about
About recursive communication: Multiple components can listen to the same event. This event is recognised by the common parent so every child can be aware of it
@posva , thanks for your information. the bus hub really works well in simple single component point to single component point communication. If there are several components with same component type, there will be problem, unfortunately, this is a normal case. Many cases, what i want to use event is to update a small data belonging to some specific component. Current event bus implementation does not give information on destination or origination node( I have seen _uid of every component, maybe we can use this unique _uid for the event wiring? ), so vuejs2.0 can not support point to point event communication in fact. To be exact, vuejs2.0 event bus only support component type to component type communication? Is there a simple solution to address that requirement: triggered by a event in the tree and update ITS OWN DATA
vuex is great for application level global static state data management, but as i understand, it maybe not good for specific local component data management?
More thought is welcome on this issue.
@cnweibo I've answered your question with an example on forum. I think this example will fill your needs. http://forum.vuejs.org/topic/4832/vue2-0-event-bus-issue-how-to-deliver-event-to-parent-when-the-same-multiple-parent-children-in-dom/6
Is there a simple solution to address that requirement: triggered by a event in the tree and update ITS OWN DATA
This is solved by simply vuex, without a complicated events system.
vuex is great for application level global static state data management, but as i understand, it maybe not good for specific local component data management
'local' means single component only. Your use case is managing state across multiple components, so its global. And vuex can have modules (state subtrees), so its not a 'global variable' kind of 'global'. You can drive groups of components with their own vuex modules.
This whole 'events vs. shared-states' discussion has been settled months ago, and the conclusion is to use either global event bus, or vuex. So I'd recommend you to read more about vuex and how it works.
On Thu, Sep 1, 2016, 16:17 cnweibo notifications@github.com wrote:
@posva https://github.com/posva , thanks for your information. the bus hub really works well in simple single component point to single component point communication. If there are several components with same component type, there will be problem, unfortunately, this is a normal case. Many cases, what i want to use event is to update a small data belonging to some specific component. Current event bus implementation does not give information on destination or origination node( I have seen _uid of every component, maybe we can use this unique _uid for the event wiring? ), so vuejs2.0 can not support point to point event communication in fact. To be exact, vuejs2.0 event bus only support component type to component type communication? Is there a simple solution to address that requirement: triggered by a event in the tree and update ITS OWN DATA vuex is great for application level global static state data management, but as i understand, it maybe not good for specific local component data management?
More thought is welcome on this issue.
โ You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/vuejs/vue/issues/3581#issuecomment-244008699, or mute the thread https://github.com/notifications/unsubscribe-auth/AFTLl1bjHqWTVDAkr8Fqbx0WiTuH16n2ks5qlooBgaJpZM4JyUfJ .
I will now close this issue, because
@ktsn , thanks for your demo fiddle. That is exactly what i want a simple solution! @fnlctrl , i will spend more time on so called modular vuex state management
thanks again~!
In my opinion $broadcast is not needed, only falling props data-flow.
Any $dispatch can be reimplemented as single-level v-on and $emit like this (in Vue 1):
<child-component @select="$emit('select', $arguments[0])" />
on each involved component
In any other situation custom event buses should be used
Really, the v-on on the component tag for custom event on the component itself work as well with $emit from the component itself. But the event data can not be retrieved in vuejs2.0
@cnweibo For the record, the event data can be retrieved.
{
template: `<foo @bar="dosomething">`,
methods: {
dosomething(params1, params2, ... and all the event data args) {}
}
}
But the event data can not be retrieved in vuejs2.0
Sure it can, what makes you think otherwise?
@fnlctrl @LinusBorg Sorry, i have misunderstanding and make things not clear. in the pattern @fnlctrl provided, really can retrieve all the data in its event handler even with
<comp @some-event-emitted-by-comp-internal-template="someFuncInParentScope"></comp>
Copy/pasting my own comment from another issue: https://github.com/vuejs/vue/issues/2760#issuecomment-250883407
I think removing $dispatch was a terrible idea. This wasn't the first ui framework/library to implement the notion of bubbling actions/events up a visual tree. It's a well established idea. Why take away this functionality on the premise that " being able to dispatch a event that causes side effects in its unknown parent tree sounds like a recipe for trouble to me"? You need to leave this responsibility to the users. I'm sure most have the common sense to use this feature appropriately. It is not a new concept!
I really can't see the benefit of this change when this library is built upon long time established web technologies/concepts such as the DOM and DOM events that do in fact bubble up the visual tree and have been doing so for years with no one complaining. Isn't the idea of components something that has been recently embraced thanks to the W3C proposal for web components? In my opinion, It only makes sense that Vue components behave similarly to regular DOM elements in regards to how event handling is done.
The proposed alternative to use a global event bus seems illogical to me when something more practical, effective, and easier to understand (due to it being a well established concept for years) already existed.
Other proposals in this thread remind me of how EmberJS wants to do it. Passing closure actions as properties to the components down each level of the hierarchy. So tedious and unnecessary! Vuejs was the reason i wrote https://www.npmjs.com/package/ember-component-action-bubbling!
Aside from this, I really like your library. But seriously, I think this was a terrible change.
The event bus paradigm was only proposed as a solution to do the exact same thing, when an event-based architecture is often times inferior to a declarative, state-based architecture.
Say, you have an app where the user can log in. With an event-based solution:
This comes with a few problems, though. The biggest one being that components that aren't being rendered in the DOM when the event is fired aren't going to receive the change, along with you not knowing which parts of the application will receive the event at all. In addition, receivers of the event can't possibly know where the event is coming from without additional information given. Excuse my language, but that's a massive clusterfuck I've dealt with and don't care to use again.
So let's use a stateful approach:
null
when signed out, and has the user's login information when signed in.Everything in the app that relies on this state variable updates accordingly. No events, and it doesn't matter when or where the component is created, because it'll always display the correct information. You could use an event to update the global state, but why do so when you can just... update it?
A declarative approach lets you write your components in a way that always appear the same way depending exactly on local/global state regardless of what the user does in your application, without having to listen to things happen. I believe this is what Vue was meant for all along, but akin to most things in software development, it took us a while to figure that out. But I'm damn well glad we did.
EDIT: Oh, and don't forget that you can watch for parameters and, say, send an AJAX request, or perform some other action when it changes. e.g., after a user logs in, watch the 'loggedIn' variable, when it's true, load their profile images, or something like that.
I understand what you're saying, but having some random component modify the global state is, I think, the same thing as having that random component bubble up an event to God knows where. You still run the very same risk of having your application's flow "dirtied" accidentally.
There are ways to handle both paradigms in a clean manner and that will usually (and should) end up being the responsibility of the framework's user.
There will be certain situations where one mechanism will make more sense than the other. For example, I agree that a logged-in state should be something known to your whole application: global state makes sense. But the button that the application user clicks will usually not handle the logic behind actually logging the user in. That will be something handled higher up the chain. It might be a component, might be a route. The button will likely only need to notify something of the intent to log in. The button, therefore, has no need to modify your global state directly.
So now, with the removal of $dispatch, you need the button component to know about some global object that manages the application's user session and notify it directly of the intent. This makes the button tightly coupled to your whole application.
Or, you might have the button nested 10 levels deep and you'd have to declare a v-on:login
handler at every level so the intent reaches its destiny. Totally unnecessary.
Actually, having to do v-on
at every level just makes your code harder to maintain.
Well obviously just directly changing the state can be problematic, but Vuex solves that issue with mutations and actions. And it's true that some solutions will fit the bill better than others, but I've never encountered a situation where declarative logic wasn't the better option.
In your specific case, I'd probably just not make a button specific to logging in, heh. In addendum, if a login-related component is nested that deep for some reason, just have it mutate global state.
The log in button was just an example. And a vuex store is what I was referring to when I mentioned "some global object". I'd have to look into how vuex works, but I expect you're tightly coupling some random component to the rest of your app's state by simply having to manage a reference to the store.
This is not desirable if, for example, the log in button was part of some third party library.
I really just wish you didn't take away the _choice _ to use either paradigm, especially when event bubbling is widely recognized and therefore easy to comprehend for new contributors on a project.
It depends on what you mean by "some random component", because a router view component, for example, has full rights to access and commit to the store in my opinion. However, if it's a smaller component for reuse, such as a button, form, or some other UI element, 9 times out of 10, there shouldn't be any logical reason for it to access the store, over using props and events.
Since data in a Vue application is top-down, you want to keep as much of your local state as top-level as possible. Deep nesting in itself is a problem to be avoided as much as possible. It's not that much of a bother to propagate events two levels down, but chances are, you might need to rethink your template structure if it goes any deeper than that.
That's mostly a tangent, though. Often times, the easier paradigms to understand are the ones that end up getting abused to the end of hell and become unwieldy. A state-based approach is much more straight-forward, as agreed upon by most of us who're currently using 2.0. You're free to continue using 1.0, or to move on to another framework if this approach isn't your preference.
9 times out of 10, there shouldn't be any logical reason for it to access the store, over using props and events.
Exactly my point.
Deep nesting in itself is a problem to be avoided as much as possible
Sometimes, this is not an option.
It's not that much of a bother to propagate events two levels down
Not so true when those levels go deeper.
the easier paradigms to understand are the ones that end up getting abused to the end of hell and become unwieldy
This should be up to your users' discipline.
You're free to continue using 1.0, or to move on to another framework if this approach isn't your preference.
Mmk.
Simplicity and convenience is the reason I was considering switching from Ember to Vue. $dispatch
is one of the things I enjoyed about Vue and removing it seems so arbitrary to me.
The team removed a lot of features for the 2.0 release. I honestly agree with all of them. Just not this one.
Thank you for your replies.
@ktsn Alternatives to $broadcast and $dispatch are very simple, this removal has come to improve and got better.
@rhyek I would like to give my 2ct about a couple of points you raised. Since the discussion has already brushed a number of topics, I would like to get back to the basics about why we deprecated $diospatch and $broacast:
If you have a parent and a deeply nested child that dispatches an event, there's no way to infer this relationship from the code (and the same is true for $broadcast
, oviously.)
If you look at the other changes and deprecations we introduced with Vue 2.0, you might realize that removing implicit behaviour in favour of explicit alternatives is a common theme, and deprecating $dispatch
fits right in there.
Example:
// parent
events: {
'some-event': function () { ... }
}
// deeply nested child:
$dispatch('some-event')
This is fine when the parent only has one direct child - but in that case, $emit()
with a listener in the template is no real extra work, either.
It becomes hard to follow (especially in teams) as soon as you have nested children (especially deeply nested), or even more than one direct child - you either have to look through all children, or rely on code comments to document which event is tiggered from which child component - which is additional boilerplate, as well.
You say that you like $dispatch and $broadcast because you don't have to pass them through other components. And I can agree that it's easier - but we have not come across many situations where this was actually nessessary, or rather: if there was such a chain of passing up an event, it would rather be the case that the data would be changed/appended/ during this trip by component in between.
when you use $dispatch
with deeply nested components, you would have to be very explicit in namespacing your events, because otherwise, they could be clashing:
// parent
events: {
'close': function () { ... }
}
// deeply nested child 1:
$dispatch('close')
// deeply nested child 2:
$dispatch('close')
..and if those children are third-party-libs, your screwed now. or have to catch the event in some component in the middle just to rename it before you $dispatch()
further up to the parent. And don't forget to comment this, because someone looking at this code might think why you do nothing other with an event than renaming it.
Using $emit and template listeners does not have this problem. you can use simple, short event names everywhere, they won't clash as each event has it's own callback attached in the template @close="callback
".
I really just wish you didn't take away the choice \ to use either paradigm,
If we thought both paradigms can work equally well, we would treat them equally. But we don't think that, for the above reasons and more.
Therefore, we try to steer users to the pactices we found to work best, while leaving a way to get around it with the "global Bus" method.
I would also like to talk about your worries about global state, but am not sure I understand your position fully yet.
Maybe you can provide an example where you think $dispatch and $broadcast work best for you, and I try to show you how "our" approach could improve the situation?
I think $dispatch/$broadcast and event bus address different things. They can make code easy to maintain and decoupled on different scenarios. If we can keep them both in, it will be great. It is very hard to say one is better than other in every cases.
@cnweibo I think there has been pretty exhaustive arguments on both sides, and honestly I don't see your point about "addressing different things". Feel free to make further arguments, but I can tell you with certainly this will not happen.
If you really really want it, it's not that hard to implement it yourself as a plugin.
@LinusBorg I truly appreciate the time you took to write your reply and I understand your arguments.
You say that you like $dispatch and $broadcast
I just like $dispatch
, honestly. $broadcast
definitely seemed odd to me. Like I said, $dispatch
is just event bubbling which is something ubiquitous among many platforms at this point. $broadcast
... not so much. The only thing similar I've ever encountered is "preview" events in WPF which are paired with normal events. They "tunnel" down the visual tree from the topmost element to the source of the original event, but they only get sent down directly down the chain of related elements and don't spread to everything.
you would have to be very explicit in namespacing your events
I agree with that and it's usually what I do, anyways. It's also what people are used to doing on jQuery. Furthermore, some platforms just send a "source" object as an argument to the handler and you could maybe filter contexts based on that (hint: instanceof
). DOM events have event.target
available, for example. Other platforms have the benefit of working with static types so this "clash" is pretty hard to encounter (an event is an instance of a class).
In any case, I just honestly don't understand why this is such a concern to the VueJS team. If people can't be careful enough about using $dispatch
, I'm sure you could find plenty other things they're doing wrong using your library. How far will you go to "protect" your users from being careless?
This is fine when the parent only has one direct child - but in that case, $emit() with a listener in the template is no real extra work, either..
Honest question (as I'm decidedly new to Vue), aside from declaring listeners at every level, don't you also have to $emit
the event on every component on the chain? That seems so annoying.
In conclusion, let me quote something someone said on an issue on vue-cli about introducing "official templates" for new projects (https://github.com/vuejs/vue-cli/issues/123#issuecomment-233071630):
As you probably know you're not locked to official templates. This gives you freedom but in same time it causes you need to do more decisions yourself.
I think at the end is all just a meter of balance. How many of these decisions can we make up front for all (most) of our users and how many or which ones users want to do on their own.
I agree with that philosophy, but it's not the same attitude I've encountered about this issue, strangely. The contrast I see between that comment and the comments here boils down to freedom. You're taking away choices based on, although good, ultimately flawed intentions.
If you really really want it, it's not that hard to implement it yourself as a plugin.
@yyx990803 That's probably what I'll end up doing... probably.
@LinusBorg also:
Therefore, we try to steer users to the pactices we found to work best, while leaving a way to get around it with the "global Bus" method.
You're not "trying to steer", you're forcing. :)
Have you tried implementing the same feature with both dispatch
and an EventBus methods?
It may help
@posva I plan to, but I mean, you basically declare the event bus object on some module, and you import it wherever else you want to $emit
, no? I don't like that, tbh. I'll definitely use it for some things, but I strongly believe it's not what I want to do every time.
In any case, I just honestly don't understand why this is such a concern to the VueJS team. If people can't be careful enough about using
$dispatch
,
Well, the point is that no one in the team could point out a real-life usecase for $dispatch()
in which it was preferable to other solutions (not only $emit()
, but also a bus, or global state), and we didn't see any in the countless forum posts about it that we answered, either.
This might be a subjective view, but maybe you can understand that if the whole team thinks "this is, in in our experience, always a inferior solution", we drop it from the core.
At this point, I want to renew my offer to discuss a real example.
I'm sure you could find plenty other things they're doing wrong using your library. How far will you go to "protect" your users from being careless?
This is of course a delicate question and a hard balance to find. We will have to judge case ยดby case.
This is fine when the parent only has one direct child - but in that case, $emit() with a listener in the template is no real extra work, either..
Honest question (as I'm decidedly new to Vue), aside from declaring listeners at every level, don't you also have to $emit the event on every component on the chain? That seems so annoying.
Since I talk about direct parent-child-releationships, you would only have to $emit() once.
If you have deeply nested children, of course you have to re-emit at every level, but to repeat myself: We haven't found nor been presented situations were dispatching across many nested children is really nessessary or preferable to other solutions.
Therefore, we try to steer users to the pactices we found to work best, while leaving a way to get around it with the "global Bus" method.
You're not "trying to steer", you're forcing. :)
I'd say we make your life a little harder - you can
We are not forcing you to use $emit()
, we ae just making your way around it a bit harder.
I think you can also add it to every Vue instance: Vue.prototype.$bus = new Vue()
Not liking something is not very constructive...
I'll be waiting for your examples ๐
@posva
I think you can also add it to every Vue instance: Vue.prototype.$bus = new Vue()
I LIKE that. Pretty clever.
Not liking something is not very constructive
I think that's a pretty valid criteria for choosing to use something or not, at least for me. In any case, I've stated many times why I think event bubbling has its place.
I'll be waiting for your examples ๐
I mean, do I really need to? I've given examples about situations where I don't like the idea of using an event bus or declaring listeners at every level. You want to see code? I could maybe come up with something to make my point a bit clearer, but I feel event bubbling is a pretty standard thing that most people can appreciate as something useful, and event buses or state managers are, at least to me, a kind of paradigm shift that, although not hard to comprehend by any stretch, seem like hipster territory. ๐
I'm of course kidding about that last comment. Like I stated before, I do see their uses and will definitely find a problem to solve with them. Actually, on some projects I've worked on using Ember, I tend to write a "service" that acts exactly like a global state manager. I assure you, I'm not trying to be stubborn about this on purpose.
I really like Vue. I just want to love it, you know?
don't you also have to $emit the event on every component
You do if you think in terms of event bubbling. And you don't if you use component composition.
E.g.:
<div>
<a-button @click="modalShown = true">Open modal</a-button>
<a-modal v-if="modalShown">
<a-button @click="modalShown = false">Close modal</a-button>
</a-modal>
</div>
a-modal
component does not need to care about events of a-button
component, because both a-modal
and two a-button
instances are "direct logical children" of a single instance, despite having complicated view hierarchy with nesting.
I think I'm just repeating myself at this point. I'll end up working around this issue, somehow. It just seems strange to me that this discussion has revolved around stating several workarounds to something pretty standard and practical that just doesn't exist anymore for whatever reason.
@LinusBorg:
Well, the point is that no one in the team could point out a real-life usecase for $dispatch() in which it was preferable to other solutions (not only $emit()
This sounds to me like a civil engineer asking me for a reason to not close off a highway exit:
He says something like, "This off-ramp leads to an intersection where people get confused about whether to turn left or right. Most people know the way because they've lived here for years, but new citizens often get lost for a couple of minutes and we want to avoid that."
I say, "Alright, sure, you can close it, that's cool. I'll just have to travel 10 km further to the next off-ramp. I'll manage."
:) Thank you all for your replies. I'm glad you're all open to discussion, at least. Seems like a good team.
This sounds to me like a civil engineer asking me for a reason to not close off a highway exit:
He says something like, "This off-ramp leads to an intersection where people get confused about whether to turn left or right. Most people know the way because they've lived here for years, but new citizens often get lost for a couple of minutes and we want to avoid that."
I'll add my bad methaphor aswell. :) In my view it's more like:
Not liking something is not very constructive
I think that's a pretty valid criteria for choosing to use something or not, at least for me. In any case, I've stated many times why I think event bubbling has its place.
I think @posva meant: "I like this" is not a constructive argument when discussing weither to keep something in, or add something to the library with a wide range of users. That's why I keep asking for a valid, real-life usecase to discuss instead of personal preferences.
Yeah, except you blew up the perfectly good highway and built another with rubberized asphalt because you read somewhere it's pretty neat. Also, it's an extra 10 km. :)
About the example: I think the one posted on the original question about recursive components is an ok one. Just imagine you want to do something at every level in sequence when an event is caught and you have a perfectly good example. Doing that with $dispatch is pretty straightforward.
You want to see code?
Yes, please
@posva I gave an example on the previous comment. Note: if you have to think more than a few seconds how to do that without $dispatch, it proves my point.
About the example: I think the one posted on the original question about recursive components is an ok one. Just imagine you want to do something at every level in sequence when an event is caught and you have a perfectly good example. Doing that with $dispatch is pretty straightforward.
with $dispatch()
// recursive-child
<template>
<recursive-child></recursive-child>
<button @click="dispatch">Do something</button>
<template>
<script>
export default{
methods: {
dispatch() { this.$dispatch('do-something') }
},
events: {
'do-something': function () {
// do something, or don't
return true // nessessary to make the event bubble up further. Don't like the un-expressivness of this
}
}
}
</script>
with $emit()
// recursive-child
<template>
<recursive-child @do-something="doSomething"></recursive-child>
<button @click="doSometing">Do something</button>
<template>
<script>
export default{
methods: {
doSomething() {
// do someting, or don't
this.$emit('do-something')
}
}
}
</script>
Is that really worse?
Got another one? :)
Ok, sure. What if they're not recursive yet still nested like that?
Alright. It's 5am here and I'm going to have a rough day thanks to you. If I come up with something better later I'll post it if not, then, either you've won or I lost interest :)
What if they're not recursive yet still nested like that?
You would have to add a @event=
listener in each template for $emit()
, and not so for $dispatch()
, that's about it.
This can be seen good or bad, depending weither you stress the verboseness vs. the expressiveness.
and btw. if in a situation you have to chain an event up till the parent this can be done something like:
<child-comp @event="$emit('event', $arguments)>
Anyways, nice discussion, enjoy your well-deserved sleep.
@rhyek it looks like workarounds only because you have determined to use $dispatch
in the first place, but that is not the goal. The goal is to allow components to communicate with one another with decent maintainability. When achieving that goal, $dispatch
is the inferior solution when you list out all the practical pros and cons, excluding preferences, so we dropped it.
Also note DOM event bubbling is fundamentally different from cross-component communication. The argument that "event bubbling is widely recognized" doesn't mean it must be a good solution for the problem we are trying to solve.
I'm done commenting in this thread because I find it hard to argue with "I just don't like it".
@yyx990803 , Hi, vuejs core team, I am struggling on find a good solution for point to point communication between parent child components with vuejs2.0. Because vuejs2.0 has deprecated the $broadcast,$dispatch api, it is very hard for me to find an alternative solution $broadcast,$dispatch provides with vuejs2.0 event bus feature. I have written a thread in the forum about this topic here, and I'd like to paste it here for more discussion.
PLEASE DO NOT CLOSE IT UNLESS have a good idea or solution on vuejs2.0. Thanks~!
In above code, when child of pcom1, for example, soncoma $emit a event, say,
on the pcom, we listen that event with
I only want pcom1 will handle that event, but unfortunately pcom2 will also handle that. How to tackle this condition?
One of the workaround in my application is to use the this.$parent as event bus In child:
In parent:
The negative point for above workaround is:
In some more complex conditions, it will be more difficult to find a good solution for the custom event system, for example, a recursive component
how recursivechild communicate to its direct pcom component? Please give your idea or point on these topics. Thanks~!