Yew: Too much boilerplate for components

Created on 29 Dec 2019  路  9Comments  路  Source: yewstack/yew

Is your feature request related to a problem? Please describe.
In order to create a component that has a bit of smarts in it, a developer needs to store the props, link, and probably some callbacks and state into the component struct.

Describe the solution you'd like
Each component lifecycle method should pass in a reference to the link and props to alleviate the burden on the developer to store those values themselves.

pub struct Context<COMP> {
    link: ComponentLink<COMP>,
    props: COMP::Properties,
}

pub trait Component: Sized + 'static {
    type Message: 'static;
    type Properties: Properties;

    fn create(ctx: &Context<Self>) -> Self;
    fn mounted(&mut self, ctx: &Context<Self>) -> ShouldRender;
    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> ShouldRender;
    fn change(&mut self, ctx: &Context<Self>, props: &Self::Properties) -> ShouldRender;
    fn view(&self, ctx: &Context<Self>) -> Html;
    fn destroy(&mut self, ctx: &Context<Self>) -> ();
}

I believe this would also remove the need to have props implement cloneable

proposal

Most helpful comment

Wouldn't the signature for change be the following instead to remove the now redundant props, given that the ctx associated with the component could be updated behind the scenes:

    fn change(&mut self, ctx: &Context<Self>) -> ShouldRender;

EDIT: I think managing change might be difficult, because it might be difficult to keep around an old props to compare against new props (which is what I assume the proposed signature attempts to support).

Also, some methods on ComponentLink require a mutable reference, eg:

    pub fn send_message(&mut self, msg: COMP::Message)

Its conceivable that you could just clone the borrowed reference to link to gain ownership if you wanted to call that method, but that method also superficially requires &mut self and could be just changed to require &self if wanted.


I'm initially not a huge fan of complicating the function signatures, but notably, only the update method would have to gain an argument and create would lose one, and in exchange Yew would stop cloning data all over the place. Additionally it sort of settles what needs how to properly handle where to put props - in that it is now decided for you by the framework - unless you need to clone a specific field for some reason.

When specialization rolls around, create could have a default implementation using Default::default, leaving just update and view to be implemented by hand, given that change's default of returning true should be adequate 90%+ of the time. In the case of pure components, just view would need to be implemented.

Overall - a huge win for removing boilerplate.

All 9 comments

Wouldn't the signature for change be the following instead to remove the now redundant props, given that the ctx associated with the component could be updated behind the scenes:

    fn change(&mut self, ctx: &Context<Self>) -> ShouldRender;

EDIT: I think managing change might be difficult, because it might be difficult to keep around an old props to compare against new props (which is what I assume the proposed signature attempts to support).

Also, some methods on ComponentLink require a mutable reference, eg:

    pub fn send_message(&mut self, msg: COMP::Message)

Its conceivable that you could just clone the borrowed reference to link to gain ownership if you wanted to call that method, but that method also superficially requires &mut self and could be just changed to require &self if wanted.


I'm initially not a huge fan of complicating the function signatures, but notably, only the update method would have to gain an argument and create would lose one, and in exchange Yew would stop cloning data all over the place. Additionally it sort of settles what needs how to properly handle where to put props - in that it is now decided for you by the framework - unless you need to clone a specific field for some reason.

When specialization rolls around, create could have a default implementation using Default::default, leaving just update and view to be implemented by hand, given that change's default of returning true should be adequate 90%+ of the time. In the case of pure components, just view would need to be implemented.

Overall - a huge win for removing boilerplate.

I assume that Context<Self> will be owned by one of those xyzState objects under the hood and passed around borrowed references. if one would want to mutate something inside of props, how would that be possible? Or would this now mean that we'll have to keep Context inside of the component struct?

I assume that you would clone prop references to acquire ownership of their values and store them in the Model if you wanted to mutate them later.

@hgzimmerman any chance you could share a pseudo-code example of how you image that would be? Just want to wrap my head around what this change might eventually look like.

I assume that Context<Self> will be owned by one of those xyzState objects under the hood and passed around borrowed references. if one would want to mutate something inside of props, how would that be possible? Or would this now mean that we'll have to keep Context inside of the component struct?

Part of the idea is that props would be immutable. Component state would be separate and would be mutable.

@jstarry wouldn't that mean such design would be contradictory to how html5 works? How would one go about changing say <input value="John">. By its design nature it is suppose to be mutable. I am applying same logic to custom components.

From reddit (https://www.reddit.com/r/rust/comments/ektzvd/announcing_the_v011_release_of_yew/fddpiqx/)

druid is using something similar https://docs.rs/druid/0.4.0/druid/widget/trait.Widget.html on their equivalent functions. It even goes so far to have distinct context versions for each trait function like UpdateContext or MountContext etc. Its quite more explicit that way without hiding it behind "magic" macros.

Another thing to consider here is making it easier to hold onto agent bridges and service tasks. Right now, devs need to fill their Component with boxed Tasks and Bridges.

if one would want to mutate something inside of props, how would that be possible? Or would this now mean that we'll have to keep Context inside of the component struct?

@trivigy I'm doing this in the application I'm currently working on, modifying a property inside Props in the create() method (and applying this modification again in the change() method). I'm going to assume it should be possible to work around this by cloning this property into the model like @hgzimmerman suggests and making changes to it there.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

djahandarie picture djahandarie  路  3Comments

kellytk picture kellytk  路  4Comments

nixpulvis picture nixpulvis  路  4Comments

thienpow picture thienpow  路  3Comments

agausmann picture agausmann  路  3Comments