diff --git a/Cargo.lock b/Cargo.lock index b560bd7eaef..b015bc8386a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1675,13 +1675,24 @@ dependencies = [ [[package]] name = "implicit-clone" -version = "0.4.1" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3d77000817fd9e7db159e8d52ed9b5941a2cdbfbdc8ca646e59887ae2b2dd1" +checksum = "fc06a255cbf402a52ae399c05a518c68c569c916ea11423620111df576b9b9bb" dependencies = [ + "implicit-clone-derive", "indexmap 2.0.2", ] +[[package]] +name = "implicit-clone-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9311685eb9a34808bbb0608ad2fcab9ae216266beca5848613e95553ac914e3b" +dependencies = [ + "quote", + "syn 2.0.38", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -3615,6 +3626,7 @@ dependencies = [ "weblog", "yew", "yew-agent", + "yew-autoprops", "yew-router", ] @@ -3876,6 +3888,17 @@ dependencies = [ "yew-agent", ] +[[package]] +name = "yew-autoprops" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58865a8f2470a6ad839274e05c9627170d32930f39a2348f8c425880a6131792" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "yew-macro" version = "0.21.0" diff --git a/packages/yew-macro/tests/classes_macro/classes-fail.stderr b/packages/yew-macro/tests/classes_macro/classes-fail.stderr index 57cf0f21fb7..9d30a817b18 100644 --- a/packages/yew-macro/tests/classes_macro/classes-fail.stderr +++ b/packages/yew-macro/tests/classes_macro/classes-fail.stderr @@ -21,7 +21,7 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied >> > > - > + > >> >> > @@ -44,7 +44,7 @@ error[E0277]: the trait bound `Classes: From<{float}>` is not satisfied >> > > - > + > >> >> > @@ -67,7 +67,7 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied >> > > - > + > >> >> > @@ -93,7 +93,7 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied >> > > - > + > >> >> > @@ -119,7 +119,7 @@ error[E0277]: the trait bound `Classes: From` is not satisfied >> > > - > + > >> >> > @@ -145,7 +145,7 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied >> > > - > + > >> >> > diff --git a/packages/yew-macro/tests/html_macro/element-fail.stderr b/packages/yew-macro/tests/html_macro/element-fail.stderr index 11f9e7d631a..bcc320a8975 100644 --- a/packages/yew-macro/tests/html_macro/element-fail.stderr +++ b/packages/yew-macro/tests/html_macro/element-fail.stderr @@ -396,76 +396,76 @@ note: function defined here | pub fn __ensure_type(_: T) {} | ^^^^^^^^^^^^^ -error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied +error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:43:26 | 43 | html! { }; - | ^^ the trait `IntoPropValue>` is not implemented for `()` + | ^^ the trait `IntoPropValue>` is not implemented for `()` | = help: the trait `IntoPropValue` is implemented for `()` -error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied +error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:44:27 | 44 | html! { }; - | ^^ the trait `IntoPropValue>` is not implemented for `()` + | ^^ the trait `IntoPropValue>` is not implemented for `()` | = help: the trait `IntoPropValue` is implemented for `()` -error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied +error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:45:22 | 45 | html! { }; - | ^^ the trait `IntoPropValue>` is not implemented for `()` + | ^^ the trait `IntoPropValue>` is not implemented for `()` | = help: the trait `IntoPropValue` is implemented for `()` -error[E0277]: the trait bound `NotToString: IntoPropValue>` is not satisfied +error[E0277]: the trait bound `NotToString: IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:46:28 | 46 | html! { }; - | ^^^^^^^^^^^ the trait `IntoPropValue>` is not implemented for `NotToString` + | ^^^^^^^^^^^ the trait `IntoPropValue>` is not implemented for `NotToString` | = help: the following other types implement trait `IntoPropValue`: - <&'static [(K, V)] as IntoPropValue>> - <&'static [T] as IntoPropValue>> + <&'static [(K, V)] as IntoPropValue>> + <&'static [T] as IntoPropValue>> <&'static str as IntoPropValue> <&'static str as IntoPropValue>> - <&'static str as IntoPropValue>> + <&'static str as IntoPropValue>> <&'static str as IntoPropValue> - <&'static str as IntoPropValue> + <&'static str as IntoPropValue> <&String as IntoPropValue> and $N others -error[E0277]: the trait bound `Option: IntoPropValue>` is not satisfied +error[E0277]: the trait bound `Option: IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:47:23 | 47 | html! { }; - | ^^^^ the trait `IntoPropValue>` is not implemented for `Option` + | ^^^^ the trait `IntoPropValue>` is not implemented for `Option` | = help: the following other types implement trait `IntoPropValue`: as IntoPropValue>> - as IntoPropValue>> - > as IntoPropValue>> + as IntoPropValue>> + > as IntoPropValue>> as IntoPropValue>>> - > as IntoPropValue>> - as IntoPropValue>> + > as IntoPropValue>> + as IntoPropValue>> > as IntoPropValue>>> as IntoPropValue> -error[E0277]: the trait bound `Option<{integer}>: IntoPropValue>` is not satisfied +error[E0277]: the trait bound `Option<{integer}>: IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:48:22 | 48 | html! { }; - | ^^^^ the trait `IntoPropValue>` is not implemented for `Option<{integer}>` + | ^^^^ the trait `IntoPropValue>` is not implemented for `Option<{integer}>` | = help: the following other types implement trait `IntoPropValue`: as IntoPropValue>> - as IntoPropValue>> - > as IntoPropValue>> + as IntoPropValue>> + > as IntoPropValue>> as IntoPropValue>>> - > as IntoPropValue>> - as IntoPropValue>> + > as IntoPropValue>> + as IntoPropValue>> > as IntoPropValue>>> as IntoPropValue> @@ -567,11 +567,11 @@ error[E0277]: the trait bound `Option: IntoPropValue | = help: the following other types implement trait `IntoPropValue`: as IntoPropValue>> - as IntoPropValue>> - > as IntoPropValue>> + as IntoPropValue>> + > as IntoPropValue>> as IntoPropValue>>> - > as IntoPropValue>> - as IntoPropValue>> + > as IntoPropValue>> + as IntoPropValue>> > as IntoPropValue>>> as IntoPropValue> = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -603,20 +603,20 @@ note: required by a bound in `yew::html::onclick::Wrapper::__macro_new` | |_^ required by this bound in `yew::html::onclick::Wrapper::__macro_new` = note: this error originates in the macro `impl_action` which comes from the expansion of the macro `impl_short` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `NotToString: IntoPropValue>` is not satisfied +error[E0277]: the trait bound `NotToString: IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:60:28 | 60 | html! { }; - | ^^^^^^^^^^^ the trait `IntoPropValue>` is not implemented for `NotToString` + | ^^^^^^^^^^^ the trait `IntoPropValue>` is not implemented for `NotToString` | = help: the following other types implement trait `IntoPropValue`: - <&'static [(K, V)] as IntoPropValue>> - <&'static [T] as IntoPropValue>> + <&'static [(K, V)] as IntoPropValue>> + <&'static [T] as IntoPropValue>> <&'static str as IntoPropValue> <&'static str as IntoPropValue>> - <&'static str as IntoPropValue>> + <&'static str as IntoPropValue>> <&'static str as IntoPropValue> - <&'static str as IntoPropValue> + <&'static str as IntoPropValue> <&String as IntoPropValue> and $N others @@ -629,15 +629,16 @@ error[E0277]: the trait bound `(): IntoPropValue` is not satisfied = help: the trait `IntoPropValue` is implemented for `()` = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `implicit_clone::unsync::IString: From<{integer}>` is not satisfied +error[E0277]: the trait bound `implicit_clone::unsync::string::IString: From<{integer}>` is not satisfied --> tests/html_macro/element-fail.rs:77:16 | 77 | html! { <@{55}> }; - | ^^ the trait `From<{integer}>` is not implemented for `implicit_clone::unsync::IString` + | ^^ the trait `From<{integer}>` is not implemented for `implicit_clone::unsync::string::IString` | = help: the following other types implement trait `From`: - > - >> - >> - > - = note: required because of the requirements on the impl of `Into` for `{integer}` + > + > + >> + >> + > + = note: required because of the requirements on the impl of `Into` for `{integer}` diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index 89cd95b4c6c..27c6c545ef8 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -27,7 +27,7 @@ yew-macro = { version = "^0.21.0", path = "../yew-macro" } thiserror = "1.0" futures = { version = "0.3", default-features = false, features = ["std"] } html-escape = { version = "0.2.13", optional = true } -implicit-clone = { version = "0.4.1", features = ["map"] } +implicit-clone = { version = "0.4.8", features = ["map"] } base64ct = { version = "1.6.0", features = ["std"], optional = true } bincode = { version = "1.3.3", optional = true } serde = { version = "1", features = ["derive"] } diff --git a/tools/website-test/Cargo.toml b/tools/website-test/Cargo.toml index e24d135286e..a930210e718 100644 --- a/tools/website-test/Cargo.toml +++ b/tools/website-test/Cargo.toml @@ -17,6 +17,7 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" weblog = "0.3.0" yew = { path = "../../packages/yew/", features = ["ssr", "csr"] } +yew-autoprops = "0.4.1" yew-router = { path = "../../packages/yew-router/" } tokio = { version = "1.33.0", features = ["rt", "macros"] } diff --git a/website/docs/concepts/function-components/properties.mdx b/website/docs/concepts/function-components/properties.mdx index d331425ec92..2927004e87f 100644 --- a/website/docs/concepts/function-components/properties.mdx +++ b/website/docs/concepts/function-components/properties.mdx @@ -60,14 +60,14 @@ pub struct Props { } #[function_component] -fn HelloWorld(props: &Props) -> Html { - html! { <>{"Am I loading? - "}{props.is_loading.clone()} } +fn HelloWorld(&Props { is_loading }: &Props) -> Html { + html! { <>{"Am I loading? - "}{is_loading} } } // Then supply the prop #[function_component] fn App() -> Html { - html! {} + html! { } } ``` @@ -91,7 +91,7 @@ fn HelloWorld() -> Html { // No props to supply #[function_component] fn App() -> Html { - html! {} + html! { } } ``` @@ -126,8 +126,8 @@ pub struct Props { } #[function_component] -fn HelloWorld(props: &Props) -> Html { - if props.is_loading.clone() { +fn HelloWorld(&Props { is_loading }: &Props) -> Html { + if is_loading { html! { "Loading" } } else { html! { "Hello world" } @@ -137,12 +137,12 @@ fn HelloWorld(props: &Props) -> Html { // Then use like this with default #[function_component] fn Case1() -> Html { - html! {} + html! { } } // Or no override the default #[function_component] fn Case2() -> Html { - html! {} + html! { } } ``` @@ -154,30 +154,36 @@ For example, to default a boolean prop to `true`, use the attribute `#[prop_or(t is evaluated when the properties are constructed and no explicit value has been given. ```rust -use yew::{function_component, html, Html, Properties}; +use yew::prelude::*; #[derive(Properties, PartialEq)] pub struct Props { + #[prop_or_default] + pub is_loading: bool, // highlight-start - #[prop_or("Bob".to_string())] + #[prop_or(AttrValue::Static("Bob"))] // highlight-end - pub name: String, + pub name: AttrValue, } #[function_component] -fn HelloWorld(props: &Props) -> Html { - html! {<>{"Hello world"}{props.name.clone()}} +fn Hello(&Props { is_loading, ref name }: &Props) -> Html { + if is_loading { + html! { "Loading" } + } else { + html! { <>{"Hello "}{name} } + } } // Then use like this with default #[function_component] fn Case1() -> Html { - html! {} + html! { } } // Or no override the default #[function_component] fn Case2() -> Html { - html! {} + html! { } } ``` @@ -188,34 +194,40 @@ Call `function` to initialize the prop value. `function` should have the signatu The function is called when no explicit value has been given for that attribute. ```rust -use yew::{function_component, html, Html, Properties}; +use yew::prelude::*; -fn create_default_name() -> String { - "Bob".to_string() +fn create_default_name() -> AttrValue { + AttrValue::Static("Bob") } #[derive(Properties, PartialEq)] pub struct Props { + #[prop_or_default] + pub is_loading: bool, // highlight-start #[prop_or_else(create_default_name)] // highlight-end - pub name: String, + pub name: AttrValue, } #[function_component] -fn HelloWorld(props: &Props) -> Html { - html! {<>{"Hello world"}{props.name.clone()}} +fn Hello(&Props { is_loading, ref name }: &Props) -> Html { + if is_loading { + html! { "Loading" } + } else { + html! { <>{"Hello "}{name} } + } } // Then use like this with default #[function_component] fn Case1() -> Html { - html! {} + html! { } } // Or no override the default #[function_component] fn Case2() -> Html { - html! {} + html! { } } ``` @@ -239,28 +251,68 @@ The macro uses the same syntax as a struct expression except that you can't use The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`). ```rust -use yew::{function_component, html, Html, Properties, props, virtual_dom::AttrValue}; +use yew::prelude::*; #[derive(Properties, PartialEq)] pub struct Props { - #[prop_or(AttrValue::from("Bob"))] + #[prop_or_default] + pub is_loading: bool, + #[prop_or(AttrValue::Static("Bob"))] pub name: AttrValue, } #[function_component] -fn HelloWorld(props: &Props) -> Html { - html! {<>{"Hello world"}{props.name.clone()}} +fn Hello(&Props { is_loading, ref name }: &Props) -> Html { + if is_loading { + html! { "Loading" } + } else { + html! { <>{"Hello "}{name} } + } } #[function_component] fn App() -> Html { // highlight-start - let pre_made_props = props! { + let pre_made_props = yew::props! { Props {} // Notice we did not need to specify name prop }; // highlight-end - html! {} + html! { } +} +``` + +## Automatically generate properties (yew-autoprops) + +In order to streamline your development process, you can also use the macro +`#[autoprops]` (from the crate `yew-autoprops`) that will automatically +generate the `Properties` struct for you. + +```rust +use yew::prelude::*; +use yew_autoprops::autoprops; + +// the #[autoprops] macro must appear BEFORE #[function_component], the order matters +#[autoprops] +#[function_component] +fn Greetings( + #[prop_or_default] + is_loading: bool, + #[prop_or(AttrValue::Static("Hello"))] + message: &AttrValue, + #[prop_or(AttrValue::Static("World"))] + name: &AttrValue, +) -> Html { + if is_loading { + html! { "Loading" } + } else { + html! { <>{message}{" "}{name} } + } } + +// The properties struct "GreetingsProps" will be generated automatically. +// +// `is_loading` will be passed as value to the components while `message` and +// `name` will use references because of the leading `&` in the definition. ``` ## Evaluation Order @@ -296,7 +348,12 @@ These include, but are not limited to: **Why is this bad?** Interior mutability (such as with `RefCell`, `Mutex`, etc.) should _generally_ be avoided. It can cause problems with re-renders (Yew doesn't know when the state has changed) so you may have to manually force a render. Like all things, it has its place. Use it with caution. -3. You tell us. Did you run into an edge-case you wish you knew about earlier? Feel free to create an issue +3. Using `Vec` type instead of `IArray`.
+ **Why is this bad?** `Vec`, just like `String`, can also be expensive to clone. `IArray` is either + a reference-counted slice (`Rc`) or a `&'static [T]`, thus very cheap to clone.
+ **Note**: `IArray` can be imported from [implicit-clone](https://crates.io/crates/implicit-clone) + See that crate to learn more. +4. You tell us. Did you run into an edge-case you wish you knew about earlier? Feel free to create an issue or PR a fix to this documentation. ## yew-autoprops