Skip to content
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

Describe a proposed way to do inter-component communication #82

Open
mitar opened this issue Oct 7, 2015 · 7 comments
Open

Describe a proposed way to do inter-component communication #82

mitar opened this issue Oct 7, 2015 · 7 comments

Comments

@mitar
Copy link
Member

mitar commented Oct 7, 2015

So describe why event driven communication in other frameworks is just a special case of communication based on reactive methods on components. So for Blaze Components, the proposed way (but not enforced) is to navigate the component tree and find your component and then call public methods on that component (possibly reactive). This allows much more complex interactions. For example, try to create some action when two events happen (mousedown and mousemove). You have to store local state for one event to know when to do something with another. When you have reactive source you can simply do things like:

Tracker.autorun(function (computation) {
  if (!this.mouseDown()) return;

  this.doSomething(this.mouseMove());
});

And if you want just normal event handling (so wait for only event and then do something and never again), you can use this pattern:

Tracker.autorun(function (computation) {
  if (!this.mouseMove()) return;
  computation.stop();

  this.doSomething();
});

Because I think the first use case is more common than the second (you more often want some declarative reaction to multiple state changes) and second one can also be done, I find this pattern more powerful. Also, you do not have any issues with event propagation and event going too far and so on. Here you explicitly find the component and attach to that reactive source.

Maybe provide some helper functions for this as well?

@bensmeets
Copy link

Had to read twice, but I get what you mean. I do get that this is more a "meteor" way and it surely has it's elegance in it. I'm worried about two things. 1) Performance and 2) Needing a fixed structure.

  1. Performance
    No numbers to back this up whatsoever, just wanting to throw this in the group. Isn't a full reactive-var and a tracker costly in regards to cheap event dispatching? We might be talking milliseconds here, but it could add up perhaps?

  2. Needing a fixed structure
    Doesn't this imply that you code knows about the structure of either it's children or it's parent? Or can we do without that? E.g., when dispatching events, I wouldn't need to know anything about my surroundings as a component, just whenever something triggers the event I'm interested in, I know what or what not to do? E.g., when dynamically creating a lot of child components, wouldn't this become a lot more difficult?

Cheers!

@mitar
Copy link
Member Author

mitar commented Oct 7, 2015

Isn't a full reactive-var and a tracker costly in regards to cheap event dispatching? We might be talking milliseconds here, but it could add up perhaps?

I would say that Tracker is probably even better because it is in fact "debounce" on every event. So if you see event as dependency invalidation, if there are multiple invalidations before flush cycle happens, then they are combined into one computation. So with Tracker you can have much more predictable running. If you have only events and you are responding to all events, you might get overwhelmed by them.

Also, invalidating a dependency is just switching one flag in the dependency. I am not sure if you can do much better than that.

So, my intuition is that it is OK or even better. So please who some numbers. :-)

Doesn't this imply that you code knows about the structure of either it's children or it's parent?

It is the same as saying that when I dispatch an event with some name, I expect that there is a listener for that event with that name. You expect that, yes? So you can do the same here. You for example search ancestors for a reactive field with a name, and if it is there, you use it, otherwise you do not.

In some way it is even better. Imagine that you have a component which has a listener for an even and expects to get those events. But you put a component into a context where nobody will be sending those events. Do you want an error then?

So my proposed approach can then support both:

  • you go and search for a reactive field in ancestors, and if you do not find one, you complain
  • or you go, and if you do not find one, then nothing

@bensmeets
Copy link

I was curious about the price difference between computation and events, so in case it's interesting it's over here http://meteorpad.com/pad/mJnAHp3HGJZJQTKmZ. You'll need to look at your console log to view some sort of numbers.

Take note, It appeared hard to get a good honest test :) So hope I did it fair enough. Also, I couldn't fabricate a right way to test a bigger count (loop) while keeping the timing measurements. So although in this example the event approach is faster, once you'll do something that fires say 1000 changes at once, it will differ (as you already said in your debounce point).

For me the bottom line was, that the performance point I had earlier is not a real life issue (for me). So although the computation syntax is totally new to me, it's a great solution.

@mitar
Copy link
Member Author

mitar commented Oct 7, 2015

Wav, thanks! So cool! Thanks for doing it!

But I think there is an issue with your measurements. Because the Tracker is computed only at flush intervals. So it is not really comparable to measure something which triggers response immediately, and something which operates on regular interval.

Also, you are using DOM events. I thought the second option is to have your own events on top of components tree. Because with DOM events you have another issue: you can send them only in one direction along the DOM tree. You cannot send an event from parent component to all descendant components (without yourself traversing them and delivering an event yourself).

@bensmeets
Copy link

Np. The current "test" (non-scientific) is as much as possible a representation for the real world current situation. So yes, the tracker will only fire once and only after invaldiation. That's why it will become as fast as events (not faster btw! which I expected it would) in a situation where the events kan be debounced. But that's it's pro, so I wanted to check what it would mean in practice.

The same goes for DOM events. That's currently the only way (I know of) to do it, without making adjustments and going around the DOM. So here goes the same as with the tracker, it can be tweaked/adjusted/built upon to make it better/faster/easier, but for now this'll work. Only for this simple test thing.

About the order of events. Although in my experience, you'll need the "bubble" (upward) phase of the event in 99,99% of the cases, it does also go downward (although limited to UI composition, you click what is visible and it will only go down to the deepest UI object you click) in the "capture" phase. So if it would be a must have, that might be an option.

This is btw the one thing that makes events a nice solution in my case. You can create truly decoupled components with their own API. A component just has it's methods an it's events. That's it. What the outside world want's to do with that API in each specific context, is their call. But as you said, that is indeed also doable with the tracker based solution. And that might be fine as well. I'm old-school, so for me the events made more sense (but not the golden egg) :)

@ghost
Copy link

ghost commented Oct 15, 2015

Context: meteor/meteor#4909 (comment)

reactive-field looks great :-), will try that!
computed-field and that too!

I dislike global states and events/messages

Well it is not actually global.

Each component has the same structure, it's recursive.
=> component may host a state and an interpreter,
=> may build a message from other messages and pass it into a higher interpreter.

The interpreter hosts a rooting table:
message click from sidebar.dashboard => do this
message click from header.title => do that
...

=> each component can be developped independently from the context, because it
takes the context to interpret the messages.
Ex: a component emit the same type of messages whatever its address is
sidebar.dashboard or main.

The topology is not included in the network of templates but rather in the rooting tables, else it makes the components to rely on an implicit context that has to be discovered by "looking up" in ancestors: it tieds them together when I'm searching for a way to split the development in parallel and merge it afterwards. Merging <=> building a routing table + interpretation of messages.

let's say that tpt4 requires some data Instead of having to rely on a chain of templates where to look up for:

  • tpt1 <- tpt2 <- tpt3 <- tpt4 ...

if tpt1 hosts a rooting table and binded tpt4 then we only have:

  • tpt1 <- tpt4

We "hope over" the other templates. This hope may be quite long in deeply nested app templates...

I'm still not finished with the Actor Model but it looks like a great way to get inspiration from... Alan Kay also!

@mitar
Copy link
Member Author

mitar commented Oct 15, 2015

Interesting. I suggest that you build this on top of Blaze Components. So they can provide you core functionality of components and then you can build on top your communication style. That would be great to have then various packages for various communication styles. (So just a different base class.) Like event-driven, reactive-variables approach I like, the one you are proposing here, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants