seed
seed copied to clipboard
Page trait proposal
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.
I'm sorry but I can't see the benefit. What concrete problem do you want to solve?
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.
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 ..
Just a note, but that particular approach is the one adopted by yew
. If I remember correctly, this is their Component
trait.
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.):
-
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).
-
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.
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.