seed-rs / seed

A Rust framework for creating web apps
MIT License
3.8k stars 153 forks source link

Page trait proposal #322

Open TatriX opened 4 years ago

TatriX commented 4 years ago

What do you think about uniting Model, Msg, update and view? Model is represented by Self in this approach.

/// Page is a trait representing one page of the application.
pub trait Page {
    type Msg: Clone + 'static;
    type View: View<Self::Msg>;

    fn update(&mut self, msg: Self::Msg, order: &mut impl Orders<Self::Msg>);
    fn view(&self) -> Self::View;
}

And the implementation:

#[derive(Default)]
pub struct HomePage {
    counter: usize,
}

#[derive(Clone)]
pub enum Msg {
    Inc,
}

impl Page for HomePage {
    type Msg = Msg;
    type View = Node<Msg>;

    fn update(&mut self, msg: Msg, orders: &mut impl Orders<Msg>) {
        match msg {
            Msg::Inc => self.counter += 1,
        }
    }

    fn view(&self) -> Self::View {
        div![format!("Got {}", self.counter)]
    }
}

so you can finally:

let app = App::builder(HomePage::default()).build_and_start();

This should simplify reasoning, because now you only need to learn 1 thing, Page trait. The rest will be guided by the compiler.

flosse commented 4 years ago

I'm sorry but I can't see the benefit. What concrete problem do you want to solve?

TatriX commented 4 years ago

It's easier to learn and should be easier to compose. In fact, what I really want is to get rid of manual dispatch

Though I haven't put enough thoughts in it yet. But anyways I think it's worth to evaluate the idea.

MuhannadAlrusayni commented 4 years ago

I agree on using traits, but not single trait for flexibility. I already opened an issue https://github.com/seed-rs/seed/issues/310 but it's now closed ..

AlterionX commented 4 years ago

Just a note, but that particular approach is the one adopted by yew. If I remember correctly, this is their Component trait.

MartinKavik commented 4 years ago

In fact, what I really want is to get rid of manual dispatch

In other words - you want to remove boilerplate (?). Boilerplate (aka wiring/plumbing) is the known trade-off for single source of truth and flexible/minimalist API in Elm architecture.


I was thinking about it and I've read some Elm books and many other sources / docs (Redux, Overmind + CerebralJs, React Hooks, HyperApp, Vuex, etc.). And I think it's the fight boilerplate vs local state or strictly defined module API (init, update, view, etc.):

  1. boilerplate vs local state - I don't want to go this way very much because I think the local state is just the "escape hatch" once you/framework fails to manage Model/Store/State properly. There is a reason why state containers exist and it's hard to decide what data should go to the global state and what to the local state. But if you want to use it I recommend to look at @rebo's hooks or wait for a solid WebComponent wrapper. But I can be wrong - please create a proposal with a new API and trade-offs for medium-big projects (at least the RealWorld example size).

  2. boilerplate vs strictly defined module API (abstraction) - strictly defined module API = all modules would have the same types for update, view, init, etc. If we want to use this approach we have to decide how the modules should pass messages and data among themselves because our users cannot design the most minimalist solution by themselves anymore. I think this can be a better way because Rust is more flexible than Elm so we can design something Seed-specific but still preserve the good parts of the Elm architecture. If you want to investigate options, please create a proposal with a new API and trade-offs for medium-big projects (at least the RealWorld example size) or create at least a new issue like Design consistent module/component API.

rebo commented 4 years ago

Having spent quite a bit of time with local state hacking vs TEA I think there are benefits (and drawbacks) to both approaches.

As Martin suggested above it's difficult to decide what would ho in local state and what correctly goes in the global/module seed model.

The fact that TEA is about as clean and explicit as you can get should not be underestimated. There is something great about having the entire app model being a self consistent representation of app state, it means following logic and assessing the impact of mutations to that state is straightforward. It also means things like undo would in theory be easier to implement because Messages or Commands are the only way to cause a state change.

Now that said the big draw back to the explicit wiring (boilerplate) is not in my mind the boilerplate itself but rather the difficulty in creating librarys of components that can be dropped into any seed app and just work. This is where React hooks have been particularly successful because they have enabled functionality to be added to apps fully encapsulated.

In my mind a proper react hooks style api could complement TEA however it would require restraint on the part of the developer.

So in short I think the current seed Model Msg api is just fine. If you give it up you lose more than you gain. However I think there is some space for that escape hatch if carefully considered.