Skip to content

Implementing Properties trait for core and std types #3862

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

LucaCappelletti94
Copy link
Contributor

@LucaCappelletti94 LucaCappelletti94 commented May 20, 2025

This PR implements the Properties trait for primitive and standard types (String, numbers, bool), eliminating boilerplate for simple components. More such types may be added as need be in the future.

This PR addresses the discussion: #3853

Therein, @WorldSEnder suggested to either use a struct wrapper or the yew-autoprops macro:

#[derive(Properties, PartialEq)]
struct TitleProps { title: String }

#[function_component]
fn Title(TitleProps { title }: &TitleProps) -> Html {
    html! { <h1>{ title }</h1> }
}

But with this PR, the code boiler plating is none, and it does not break API compatibility:

#[function_component]
fn Title(title: &String) -> Html {
    html! { <h1>{ title }</h1> }
}

Copy link

github-actions bot commented May 20, 2025

Visit the preview URL for this PR (updated for commit 7af22ac):

https://yew-rs-api--pr3862-string-properties-twzzvax5.web.app

(expires Tue, 27 May 2025 07:23:48 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Copy link

github-actions bot commented May 20, 2025

Benchmark - core

Yew Master

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  1.837 ns      │ 3.65 ns       │ 1.844 ns      │ 1.886 ns      │ 100     │ 1000000000

Pull Request

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.151 ns      │ 2.483 ns      │ 2.154 ns      │ 2.166 ns      │ 100     │ 1000000000

Copy link

github-actions bot commented May 20, 2025

Benchmark - SSR

Yew Master

Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 310.484 311.115 310.690 0.176
Hello World 10 474.825 482.597 477.876 3.259
Function Router 10 1616.054 1633.917 1624.304 5.069
Concurrent Task 10 1005.635 1007.333 1006.322 0.515
Many Providers 10 1041.871 1075.159 1061.666 10.053

Pull Request

Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 310.463 320.559 311.938 3.192
Hello World 10 493.073 504.382 497.967 3.011
Function Router 10 1640.585 1676.570 1655.886 10.150
Concurrent Task 10 1005.969 1007.272 1006.598 0.347
Many Providers 10 1097.289 1128.531 1107.909 9.609

Copy link

github-actions bot commented May 20, 2025

Size Comparison

examples master (KB) pull request (KB) diff (KB) diff (%)
async_clock 99.875 99.875 0 0.000%
boids 168.626 168.626 0 0.000%
communication_child_to_parent 91.946 91.946 0 0.000%
communication_grandchild_with_grandparent 103.109 103.109 0 0.000%
communication_grandparent_to_grandchild 98.136 98.136 0 0.000%
communication_parent_to_child 87.824 87.824 0 0.000%
contexts 104.237 104.237 0 0.000%
counter 84.528 84.528 0 0.000%
counter_functional 84.947 84.947 0 0.000%
dyn_create_destroy_apps 87.456 87.456 0 0.000%
file_upload 98.718 98.718 0 0.000%
function_memory_game 170.622 170.622 0 0.000%
function_router 338.587 338.587 0 0.000%
function_todomvc 163.811 163.811 0 0.000%
futures 236.699 236.699 0 0.000%
game_of_life 104.732 104.732 0 0.000%
immutable 194.302 194.302 0 0.000%
inner_html 80.185 80.185 0 0.000%
js_callback 107.682 107.682 0 0.000%
keyed_list 195.888 195.888 0 0.000%
mount_point 83.499 83.499 0 0.000%
nested_list 112.869 112.869 0 0.000%
node_refs 90.834 90.834 0 0.000%
password_strength 1785.698 1785.698 0 0.000%
portals 92.775 92.775 0 0.000%
router 307.286 307.286 0 0.000%
suspense 111.678 111.678 0 0.000%
timer 88.630 88.630 0 0.000%
timer_functional 94.843 94.843 0 0.000%
todomvc 143.790 143.790 0 0.000%
two_apps 85.897 85.897 0 0.000%
web_worker_fib 134.989 134.989 0 0.000%
web_worker_prime 185.544 185.544 0 0.000%
webgl 83.112 83.112 0 0.000%

✅ None of the examples has changed their size significantly.

@LucaCappelletti94
Copy link
Contributor Author

There seems to be some non-determinism in the order of the expected error messages. I will split that test into one separate test for each of the verified compile-time panic cases.

@WorldSEnder
Copy link
Member

How would a component with a primitive property type (such as String) end up being used? There seem to be no configurable properties. The PR contains no code or tests that end up using impl Properties for String or its related builder.

@LucaCappelletti94
Copy link
Contributor Author

I will most definitely add tests, but here is an example showing how I use it in my own code.

There are a handful of cases where I want to create very simple components, which have a single property such as a String or any other simple core type - let's take the fontawesome icon as an example.

Without this PR, the code would look like the following:

#[derive(Properties, PartialEq)]
struct FAIconProps {
    icon: String,
}

#[function_component(FAIcon)]
/// Renders a Font Awesome icon.
pub fn font_awesome_icon(props: &FAIconProps) -> Html {
    html! {
        <i class={format!("fa-brands fa-{}", props.icon.clone())} aria-hidden="true"></i>
    }
}

Within this PR branch, I can define the component without any additional boiler plating:

#[function_component(FAIcon)]
/// Renders a Font Awesome icon.
pub fn font_awesome_icon(icon: &String) -> Html {
    html! {
        <i class={format!("fa-solid fa-{icon}")} aria-hidden="true"></i>
    }
}

And then, to use it I do the following:

#[derive(Properties, PartialEq)]
pub struct ParentProps {
   icon: String,
   url: String,
   name: String
}

#[function_component(ParentComponent)]
pub fn parent_component(props: &ParentProps) -> Html {
    let url = props.url;
    let name = props.name;
    html! {
        <a class="login-provider" href={url}>
            <FAIcon ..props.icon.clone() />
            {format!("Login with {name}")}
        </a>
    }
}

I believe this option makes it quite clean. Of course, it solely applies to such simple small components.

@WorldSEnder
Copy link
Member

So as a consumer, you'd have to write <FAIcon ..props.icon.clone() />. I don't expect a good user experience from this.

First of all, most of the machinery with ImplicitClone and properties work on individual properties, not the whole Property struct. Secondly, the Properties derive macro is expected to put methods such as builder there that are internal and would potentially have to be adjusted if we want to add better lints, or change the way properties are initialized.

Basically it boils down to this: should a single property be usable without having to wrap it in a struct of properties? Currently, the answer is no. And right now; I'd much rather like an approach that let's you write

#[function_component(props(generate_bikeshedding))]
/// Renders a Font Awesome icon.
pub fn FAIcon(icon: &String) -> Html {
    html! {
        <i class={format!("fa-brands fa-{icon}")} aria-hidden="true"></i>
    }
}
// ... later
<FAIcon icon={props.icon} />

which should work without changing the answer to the above question and without having to implement Properties for basically everything that is usable as a single property. Besides, giving your single property a name is imo good habit anyway, in case you ever want to change or add other properties in the future.

@LucaCappelletti94
Copy link
Contributor Author

Ok, then I would proceed to close the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants