Refactor: move Component docs into the components directory to reflect how they are presented in the guide

This commit is contained in:
Reinis Mazeiks committed 2022-02-10 10:07:33 +02:00
1 parent 67766c7b59
commit 02d8e66f40
7 files changed
+6 -6

No files matched your search

+192
View File
@@ -0,0 +1,192 @@
# Component Properties
All component `properties` must implement the `Properties` trait. The `Props` macro automatically derives this trait but adds some additional functionality. In this section, we'll learn about:
- Using the props macro
- Memoization through PartialEq
- Optional fields on props
- The inline_props macro
## 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
Yes - *another* macro! However, this one is entirely optional.
For internal components, we provide the `inline_props` macro, which will let you embed your `Props` definition right into the function arguments of your component.
Our title card above would be transformed from:
```rust
#[derive(Props, PartialEq)]
struct TitleCardProps {
title: String,
}
fn TitleCard(cx: Scope<TitleCardProps>) -> Element {
cx.render(rsx!{
h1 { "{cx.props.title}" }
})
}
```
to:
```rust
#[inline_props]
fn TitleCard(cx: Scope, title: String) -> Element {
cx.render(rsx!{
h1 { "{title}" }
})
}
```
Again, this macro is optional and should not be used by library authors since you have less fine-grained control over documentation and optionality.
However, it's great for quickly throwing together an app without dealing with *any* extra boilerplate.