How rsx macro works
As described in the guide,
frender interprets rsx!( <MyComponent prop={value} /> )
as the builder pattern:
MyComponent::create_element(
MyComponent::Props::init_builder()
.prop(value)
.build()
)
But how does the builder pattern know whether a prop is required or optional? Here I will explain the implementation details.
Completely optional props
If the properties of a props struct are all optional, it would be easy to implement the builder pattern. We can use the struct itself as the builder type.
struct MyProps { // defaults to None optional_name: Option<String>, // Optional prop does not need to be Option<T>. // It just need to implement Default. // The following prop defaults to 0. optional_num: i32 } impl frender::react::Props for MyProps { type InitialBuilder = Self; fn init_builder() -> Self { Self { optional_name: Default::default(), optional_num: Default::default(), } } } // builder methods impl MyProps { fn optional_name(mut self, value: Option<String>) -> Self { self.optional_name = value; self } fn optional_num(mut self, value: i32) -> Self { self.optional_num = value; self } } impl frender::react::PropsBuilder<MyProps> for MyProps { fn build(self) -> MyProps { self } } fn main() { let props = MyProps::init_builder() .optional_name("frender".to_string()) .build(); assert_eq!(props.optional_name, "frender"); assert_eq!(props.optional_num, 0); }
Props with required fields
#![allow(unused)] fn main() { struct PropertyAlreadySet<T>(T); struct PropertyNotSet; struct MyProps { // required property pub name: String, // optional property, defaults to None pub optional_message: Option<String>, } impl frender::react::Props for MyProps { type InitialBuilder = MyPropsBuilder; fn init_builder() -> MyPropsBuilder { MyPropsBuilder { // required field is initialized as `PropertyNotSet` name: PropertyNotSet, // optional field is initialized as default optional_message: Default::default(), } } } struct MyPropsBuilder<MyPropsBuilder__name> { name: MyPropsBuilder__name, optional_message: Option<String>, } impl<MyPropsBuilder__name> MyPropsBuilder<MyPropsBuilder__name> { pub fn name(self, value: String) -> MyPropsBuilder<PropertyAlreadySet<String>> { MyPropsBuilder { name: value, optional_message: self.optional_message, } } pub fn optional_message(mut self, value: Option<String>) -> Self { self.optional_message = value; self } } impl frender::react::PropsBuilder<MyProps> for MyPropsBuilder<PropertyAlreadySet<String>> { fn build(self) -> MyProps { MyProps { name: self.name.0, optional_message: self.optional_message, } } } }
def_props
macro
Using frender, you don't need to
implement the above yourself.
All of the details is encapsulated
in the def_props
macro.
#![allow(unused)] fn main() { extern crate frender; use frender::prelude::*; def_props! { struct MyProps { // required property pub name: String, // optional property, defaults to None pub optional_message?: Option<String>, } } }