|
@@ -9,6 +9,151 @@ All component `properties` must implement the `Properties` trait. The `Props` ma
|
|
|
|
|
|
|
|
|
|
|
|
+## Using the Props Macro
|
|
|
+
|
|
|
+All `properties` that your components take must implement the `Properties` trait. The simplest props you can use is simply `()` - or no value at all. `Scope` is generic over your component's props and actually defaults to `()`.
|
|
|
+
|
|
|
+```rust
|
|
|
+// this scope
|
|
|
+Scope<()>
|
|
|
+
|
|
|
+// is the same as this scope
|
|
|
+Scope
|
|
|
+```
|
|
|
+
|
|
|
+If we wanted to define a component with its own props, we would create a new struct and tack on the `Props` derive macro:
|
|
|
+
|
|
|
+```rust
|
|
|
+#[derive(Props)]
|
|
|
+struct MyProps {
|
|
|
+ name: String
|
|
|
+}
|
|
|
+```
|
|
|
+This particular code will not compile - all `Props` must either a) borrow from their parent or b) implement `PartialEq`. Since our props do not borrow from their parent, they are `'static` and must implement PartialEq.
|
|
|
+
|
|
|
+For an owned example:
|
|
|
+```rust
|
|
|
+#[derive(Props, PartialEq)]
|
|
|
+struct MyProps {
|
|
|
+ name: String
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+For a borrowed example:
|
|
|
+```rust
|
|
|
+#[derive(Props)]
|
|
|
+struct MyProps<'a> {
|
|
|
+ name: &'a str
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Then, to use these props in our component, we simply swap out the generic parameter on scope.
|
|
|
+
|
|
|
+For owned props, we just drop it in:
|
|
|
+
|
|
|
+```rust
|
|
|
+fn Demo(cx: Scope<MyProps>) -> Element {
|
|
|
+ todo!()
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+However, for props that borrow data, we need to explicitly declare lifetimes. Rust does not know that our props and our component share the same lifetime, so must explicitly attach a lifetime in two places:
|
|
|
+
|
|
|
+```rust
|
|
|
+fn Demo<'a>(cx: Scope<'a, MyProps<'a>>) -> Element {
|
|
|
+ todo!()
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+By putting the `'a` lifetime on Scope and our Props, we can now borrow data from our parent and pass it on to our children.
|
|
|
+
|
|
|
+
|
|
|
+## Memoization
|
|
|
+
|
|
|
+If you're coming from React, you might be wondering how memoization fits in. For our purpose, memoization is the process in which we check if a component actually needs to be re-rendered when its props change. If a component's properties change but they wouldn't necessarily affect the output, then we don't need to actually re-render the component.
|
|
|
+
|
|
|
+For example, let's say we have a component that has two children:
|
|
|
+
|
|
|
+```rust
|
|
|
+fn Demo(cx: Scope) -> Element {
|
|
|
+ let name = use_state(&cx, || String::from("bob"));
|
|
|
+ let age = use_state(&cx, || 21);
|
|
|
+
|
|
|
+ cx.render(rsx!{
|
|
|
+ Name { name: name }
|
|
|
+ Age { age: age }
|
|
|
+ })
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+If `name` changes but `age` does not, then there is no reason to re-render our `Age` component since the contents of its props did not meaningfully change.
|
|
|
+
|
|
|
+
|
|
|
+Dioxus implements memoization by default, which means you can always rely on props with `PartialEq` or no props at all to act as barriers in your app. This can be extremely useful when building larger apps where properties frequently change. By moving our state into a global state management solution, we can achieve precise, surgical re-renders, improving the performance of our app.
|
|
|
+
|
|
|
+
|
|
|
+However, for components that borrow values from their parents, we cannot safely memoize them.
|
|
|
+
|
|
|
+For example, this component borrows `&str` - and if the parent re-renders, then the actual reference to `str` will probably be different. Since the data is borrowed, we need to pass a new version down the tree.
|
|
|
+
|
|
|
+```rust
|
|
|
+#[derive(Props)]
|
|
|
+struct MyProps<'a> {
|
|
|
+ name: &'a str
|
|
|
+}
|
|
|
+
|
|
|
+fn Demo<'a>(cx: Scope<'a, MyProps<'a>>) -> Element {
|
|
|
+ todo!()
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+TLDR:
|
|
|
+- if you see props with a lifetime or generics, it cannot be memoized
|
|
|
+- memoization is done automatically through the `PartialEq` trait
|
|
|
+- components with empty props can act as memoization barriers
|
|
|
+
|
|
|
+## Optional Fields
|
|
|
+
|
|
|
+Dioxus' `Props` macro is very similar to [@idanarye](https://github.com/idanarye)'s [TypedBuilder crate](https://github.com/idanarye/rust-typed-builder) and supports many of the same parameters.
|
|
|
+
|
|
|
+For example, you can easily create optional fields by attaching the `optional` modifier to a field.
|
|
|
+
|
|
|
+```rust
|
|
|
+#[derive(Props, PartialEq)]
|
|
|
+struct MyProps {
|
|
|
+ name: String,
|
|
|
+
|
|
|
+ #[props(optional)]
|
|
|
+ description: Option<String>
|
|
|
+}
|
|
|
+
|
|
|
+fn Demo(cx: MyProps) -> Element {
|
|
|
+ ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Then, we can completely omit the description field when calling the component:
|
|
|
+
|
|
|
+```rust
|
|
|
+rsx!{
|
|
|
+ Demo {
|
|
|
+ name: "Thing".to_string(),
|
|
|
+ // description is omitted
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+The `optional` modifier is a combination of two separate modifiers: `default` and `strip_option`. The full list of modifiers includes:
|
|
|
+
|
|
|
+- `default` - automatically add the field using its `Default` implementation
|
|
|
+- `strip_option` - automatically wrap values at the call site in `Some`
|
|
|
+- `optional` - combine both `default` and `strip_option`
|
|
|
+- `into` - automatically call `into` on the value at the callsite
|
|
|
+
|
|
|
+For more information on how tags work, check out the [TypedBuilder](https://github.com/idanarye/rust-typed-builder) crate. However, all attributes for props in Dioxus are flattened (no need for `setter` syntax) and the `optional` field is new.
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
## The inline_props macro
|
|
|
|