Giter Club home page Giter Club logo

Comments (13)

Pauan avatar Pauan commented on May 19, 2024 1

As I understand, the Msgs would be sent from the current Component to itself.

Yes, that's correct. I mentioned earlier in this thread that that is a big limitation of the Component trait.

Do you have any thoughts on how to implement such communication?

I've talked more about that here.

I don't have any particular recommendations right now. There's so many different ways to handle communication (with so many different tradeoffs).

So I think all of the various options should be experimented with before we decide on a "blessed" option. Thankfully, dominator is flexible enough that this experimentation doesn't require changes to dominator itself.

So the experimentation can happen quite quickly, it just requires somebody to do it, and try out various options in a real project.

Passing "props" down to child components

As a general rule, that is handled by either passing values into a struct, or passing arguments to the render method.

Notifying parent of child events or child state changes

This is where things get tricky. You can use streams, queues, event dispatchers, traits, all sorts of options.

Passing the parent a real DOM ref of the child (can be useful for example for scrolling the child view or focusing it etc)

This is of course doable right now: dominator gives you access to raw DOM nodes, and you can use the above-mentioned methods to pass it upwards.

I don't think it's a great idea though. I think it's better to encapsulate that in some sort of API, rather than exposing internal details.

from rust-dominator.

limira avatar limira commented on May 19, 2024

To make it clear, this proposal is not about forbidding state change in the renderer. But just the ability move the mutating codes out of the renderer and the renderer still need to make a call to the moved-out code.

from rust-dominator.

Pauan avatar Pauan commented on May 19, 2024

Thanks for creating this issue!

This is a deep and nuanced topic. In particular, Rust's general lack of global variables means that the typical Elm-style of architecture is more complicated.

So I have a question for you: since you don't want any state changes inside the render function, where do you think those state changes should go instead? Into a global variable? Into a trait?

from rust-dominator.

limira avatar limira commented on May 19, 2024

I'm not experienced enough to say exactly how I want it to be, especially for a crate that I never use before. If you are happy with how it is right now, just ignore this. I may use this crate in the future, then, I may have some thoughts.

from rust-dominator.

limira avatar limira commented on May 19, 2024

I just do an experiment here. It may be one of the ways to solve this. Admittedly, I am not sure if there is any problem with my approach because I have little knowledge about JS.

from rust-dominator.

Pauan avatar Pauan commented on May 19, 2024

Yeah, I saw that. I think it's a cool experiment, though I don't think it requires a new framework: it should be possible to do something similar with dominator.

from rust-dominator.

limira avatar limira commented on May 19, 2024

I don't think it requires a new framework

That's why I do not release it on crates.io yet. And do not bother to register a placeholder for mico.

from rust-dominator.

Pauan avatar Pauan commented on May 19, 2024

Untested, but something like this should work:

pub trait Component {
    type Message;

    fn update(&mut self, message: Self::Message);

    fn render(&self, messages: Messages<Self>) -> Dom;
}


pub struct Messages<A> {
    state: Rc<RefCell<A>>,
}

impl<A> Messages<A> where A: Component {
    pub fn push(&self, message: A::Message) {
        let state = self.state.borrow_mut();
        state.update(message);
    }
}

impl<A> Clone for Messages<A> {
    fn clone(&self) -> Self {
        Messages {
            state: self.state.clone(),
        }
    }
}


pub fn component<A: Component>(c: A) -> Dom {
    let messages = Messages {
        state: Rc::new(RefCell::new(c)),
    };

    let state = messages.state.borrow();

    state.render(messages.clone())
}

Now you can define components like this:

pub struct State {
    // ...
}

impl State {
    pub fn new() -> Self {
        // ...
    }
}

enum Message {
    Foo,
    Bar,
}

impl Component for State {
    type Message = Message;

    fn update(&mut self, message: Self::Message) {
        // ...
    }

    fn render(&self, messages: Messages<Self>) -> Dom {
        html!("div", {
            .event(clone!(messages => move |_: MouseClick| {
                messages.push(Message::Foo);
            }))
        })
    }
}

And then you can create and use those components like this:

html!("div", {
    .children(&mut [
        component(State::new()),
    ])
})

This seems like a pretty good system. It's reasonably light-weight, and it does have a clean separation between updating and rendering.

It avoids a lot of the problems of heavy-weight components, because it's using a light-weight trait + Rc + RefCell system.

Unfortunately it doesn't solve the "push messages to higher components" problem, this is only for self-contained components.

from rust-dominator.

Pauan avatar Pauan commented on May 19, 2024

By the way, you can use the above Component system immediately with dominator, you don't need to wait for me to add anything.

I've tried hard to make dominator flexible enough so that systems like Component can be added without any changes to dominator itself.

from rust-dominator.

limira avatar limira commented on May 19, 2024

pub trait Component

Unfortunately it doesn't solve the "push messages to higher components" problem, this is only for self-contained components.

In Mico, the trait Component above is actually the trait Application of which only one instance is allowed. If a user want to make reusable components (just a function or normal struct), they can just pass the argument main: &WeakMain<A> around, and register an event with it:

Button::new()
    .text("Click me")
    .on_click(main, move |_| Msg::Foo),

then, every messages are triggered on the app-state. Such a component cannot have it own state. But with the help of futures-signals, it's possible for an app to put everything (data) in a single app-state with very little performance lost.

As I understand, your solution may behave the same by wrapping everything in one single Component and passing the cloned of messages: Messages<Self> around. It just feels a little less elegant than Mico, especially the way events are registered, because this is just an add-in solution.

from rust-dominator.

Pauan avatar Pauan commented on May 19, 2024

Such a component cannot have it own state. But with the help of futures-signals, it's possible for an app to put everything (data) in a single app-state with very little performance lost.

The purpose of components isn't for performance (in Elm, creating components does not increase performance).

The reason for components is to create encapsulated reusable libraries. This requires a clean separation of state. The library cannot use your app's state, since then it won't be a reusable library.

But let's say you aren't interested in reusable libraries, and you write all your code yourself. Even then, you can't just write your app as a single huge 50,000 line component, so you need to split it up into multiple components. And state encapsulation is really critical to splitting things up in a way that is manageable.

These are difficult problems, which I've given a lot of thought to. I don't have a good solution, but I don't think the solution is to have state separation only for the application. I think it's important to be able to create separate encapsulated components.

especially the way events are registered, because this is just an add-in solution

It's true that a specialized framework (like mico) will be able to provide a smoother experience compared to a flexible framework like dominator. But with mixins I don't think the situation is too bad.

Untested, but something like this should work:

#[inline]
fn push_message<A, B, E, F>(messages: &Messages<Self>, f: F) -> impl FnOnce(DomBuilder<B>) -> DomBuilder<B>
    where A: Component + 'static,
          B: IEventTarget + Clone + 'static,
          E: ConcreteEvent,
          F: FnMut(E) -> A::Message + 'static {
    let messages = messages.clone();

    #[inline]
    move |dom| dom
        .event(move |e: E| {
            messages.push(f(e));
        })
}

And now you use it like this:

html!("div", {
    .apply(push_message(&messages, |_: MouseClick| Message::Foo))
})

I think I should add in macro support to html, then you would be able to write an event macro which is even more convenient:

html!("div", {
    .event!(messages, MouseClick => Message::Foo)
})

I think it's possible to have both flexibility and convenience.

from rust-dominator.

tiberiusferreira avatar tiberiusferreira commented on May 19, 2024

Hello, sorry if this is not the right place to ask this. I'm trying understand how to create reusable "components".

As far as I know, #2 (comment) is the recommended way of doing this currently, right?

However, I still don't understand how parent-child communication (and vice-versa) is addressed by that or even the Component traits discussed here. As I understand, the Msgs would be sent from the current Component to itself.

Do you have any thoughts on how to implement such communication?
I'm specially interested in

  • Passing "props" down to child components
  • Notifying parent of child events or child state changes
  • Passing the parent a real DOM ref of the child (can be useful for example for scrolling the child view or focusing it etc)

and doing so in a way which is fully encapsulated.

from rust-dominator.

limira avatar limira commented on May 19, 2024

The purpose of components isn't for performance (in Elm, creating components does not increase performance).

I only have very limited experience with React (few weeks), and some with Yew (two or three months) before starting my Simi (which is an incremental virtual DOM framework). Never use any other frameworks (include Elm).

But my thought is that, in virtual DOM approach, a framework could just perform rerender vDOM + diffing + apply changes only for the component that its state did change, not the whole app's vDOM. That means a better performance for the framework. I don't know how Elm work to say anything about it.

We are now actually being helped by signals (not in the vDOM world), a state-less component, just need a reference to the required data from the single-big-state of the app, still able to trigger the right part of the code to update the view. But it's only my thought, not sure how to implement it yet.

from rust-dominator.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.