DEV Community

Discussion on: Let's Build a Rust Frontend with Yew - Part 2

 
deciduously profile image
Ben Lovy • Edited

The type of msg here is simply crate::Msg. The trick is that the onsignal prop is a closure, and the return value of the closure is a Yew message. This whole closure is the prop that's passed in as a callback. When this closure is invoked Yew will dispatch whatever message it's called with via the lib.rs update function. The actual call happens in the child:

fn update(&mut self, msg: Self::Message) -> ShouldRender {
    match msg {
      Msg::ButtonPressed(msg) => {
        if let Some(ref mut callback) = self.onsignal {
          callback.emit(msg);
        }
      }
    }
    false
  }

callback.emit() is where the parent event is actually fired. The specific message that's getting passed up is pulled out of ButtonPressed(msg). When the ButtonPressed event happens as a result of the onclick handler, the child component checks to see if there is was a callback provided by the parent - there is, it's |msg| msg. Then it emits that callback, filling in msg with the message provided by the ButtonPressed event, which will be a crate::Msg::SwitchRoom() with the right target room. This is why you got a compiler error - right here you're promising to call it with a crate::Msg, without the option. You're correct in assuming the source of that error should probably be where you were putting it, not the child component - it's the child that should complain and prompt you to use the correct type. Unfortunately the html! macro expansion obfuscates this because it expands prior to actually evaluating and throws first. At least as far as I understand it.

So, the child can't really see the parent, it just invokes the closure that the parent passed in as a prop.

Does that answer your question? I find callbacks a little tricky to talk about clearly, so let me know if I'm just making it more confusing.

Thread Thread
 
ademi profile image
ademi

Perfectly put :)

Thank for the detailed explanation, now it things make more sense!

For some reason I assumed that the child gets created ( the create method of the child gets called) at the parent html!{} closure, never which caused most of my confusion. While ,at least what I understand from your explanation, the child update function gets called at that stage.

As a follow up question, where would the child starts to exist? and how is the parameters get supplied to the create function?

Thanks again, and sorry for keeping coming back with more questions.

Thread Thread
 
deciduously profile image
Ben Lovy • Edited

Don't apologize! It's a good check to make sure I know what the heck I'm talking about too.

create() is only called once, in main():

fn main() {
  yew::initialize();
  let app: App<Model> = App::new();
  app.mount_to_body();  // <- right here
  yew::run_loop();
}

yew::initialize() is actually just stdweb::initialize(), it sets up the interop config.

The components are all created during mount_to_body. By the time we hit mount_to_body(), though, the html! macros have already expanded into a yew::virtual_dom::VNode tree, because macro expansion happened before the program was evaluated. The VDOM already is built at compile time, and create() is called with what you pass in in html!.

When we implemented the Component trait on this type, we created the ComponentLink struct. We don't use it in create, but it's still available to the component to use in update and change. This, through a few nested types, holds the pointer we need:

pub struct ComponentLink<COMP: Component> {
    scope: Scope<COMP>,
}

pub struct Scope<COMP: Component> {
    shared_component: Shared<Option<ComponentRunnable<COMP>>>,
}

struct ComponentRunnable<COMP: Component> {
    env: Scope<COMP>,
    component: Option<COMP>,           
    last_frame: Option<VNode<COMP>>,
    element: Element,
    ancestor: Option<VNode<COMP>>,     // <- right here
    occupied: Option<NodeCell>,
    init_props: Option<COMP::Properties>,
    destroyed: bool,
}

The update function is called every time a component receives a Msg. When the app is running, each component is sort of like an actor in the Actor model inside the event loop initialized with yew::run_loop().

Caveat to this answer being there's not much documentation or comments for me to source, but the Yew codebase is pretty small and generally readable. I believe I'm at least mostly correct :)

Thread Thread
 
ademi profile image
ademi

Fair enough,

To be honest, I couldn't yet swallow Rust way of doing things, but our conversation does clear a lot of my doubts.

Thanks a lot, Yew seems very powerful and promising framework. It's worth spending time studying the source code carefully.

Thanks again,
Cheers :)

Thread Thread
 
deciduously profile image
Ben Lovy

This code, too, isn't necessarily indicative of "normal" Rust, Yew imposes a rigid idiosyncratic model on top of it. Rust is a great, flexible language and i hope you continue to explore!

Thread Thread
 
ademi profile image
ademi

definitely!