mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-14 16:37:14 +00:00
Merge branch 'master' of https://github.com/mrxiaozhuox/dioxus
This commit is contained in:
commit
15a9c37a36
14 changed files with 293 additions and 485 deletions
|
@ -9,16 +9,11 @@
|
|||
- [Conditional Rendering](elements/conditional_rendering.md)
|
||||
- [Lists](elements/lists.md)
|
||||
- [Special Attributes](elements/special_attributes.md)
|
||||
- [Components](elements/components.md)
|
||||
- [Properties](elements/propsmacro.md)
|
||||
- [Reusing, Importing, and Exporting](elements/exporting_components.md)
|
||||
- [Children and Attributes](elements/component_children.md)
|
||||
- [How Data Flows](elements/composing.md)
|
||||
- [Thinking Reactively]()
|
||||
- [Thinking Reactively]()
|
||||
- [Thinking Reactively]()
|
||||
- [Thinking Reactively]()
|
||||
- [Thinking Reactively]()
|
||||
- [Components](components/index.md)
|
||||
- [Properties](components/propsmacro.md)
|
||||
- [Reusing, Importing, and Exporting](components/exporting_components.md)
|
||||
- [Children and Attributes](components/component_children.md)
|
||||
- [How Data Flows](components/composing.md)
|
||||
- [Adding Interactivity](interactivity/index.md)
|
||||
- [Hooks and Internal State](interactivity/hooks.md)
|
||||
- [UseState and UseRef](interactivity/importanthooks.md)
|
||||
|
|
|
@ -215,5 +215,3 @@ In this chapter, we learned:
|
|||
- How to convert `listeners` into `EventHandlers` for components
|
||||
- How to extend any node with custom attributes and children
|
||||
|
||||
Next chapter, we'll talk about conditionally rendering parts of your user interface.
|
||||
|
BIN
docs/guide/src/components/component_example_title.png
Normal file
BIN
docs/guide/src/components/component_example_title.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
BIN
docs/guide/src/components/component_example_votes.png
Normal file
BIN
docs/guide/src/components/component_example_votes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
|
@ -1,6 +1,6 @@
|
|||
# Thinking in React
|
||||
|
||||
We've finally reached the point in our tutorial where we can talk about the "Theory of React." We've talked about defining a declarative view, but not about the aspects that make our code *reactive*.
|
||||
We've finally reached the point in our tutorial where we can talk about the theory of Reactive interfaces. We've talked about defining a declarative view, but not about the aspects that make our code *reactive*.
|
||||
|
||||
Understanding the theory of reactive programming is essential to making sense of Dioxus and writing effective, performant UIs.
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
# Reusing, Importing, and Exporting Components
|
||||
|
||||
As your application grows in size, you'll want to start breaking your UI into components and, eventually, different files. This is a great idea to encapsulate functionality of your UI and scale your team.
|
||||
|
@ -67,6 +66,8 @@ fn ActionCard(Scope<ActionCardProps>) -> Element {}
|
|||
We should also create a `mod.rs` file in the `post` folder so we can use it from our `main.rs`. Our `Post` component and its props will go into this file.
|
||||
|
||||
```rust
|
||||
// src/post/mod.rs
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
|
@ -289,14 +290,3 @@ use dioxus::prelude::*;
|
|||
pub struct ActionCardProps {}
|
||||
pub fn ActionCard(Scope<ActionCardProps>) -> Element {}
|
||||
```
|
||||
|
||||
## Moving forward
|
||||
|
||||
Next chapter, we'll start to add use code to hide and show Elements with conditional rendering.
|
||||
|
||||
For more reading on components:
|
||||
|
||||
- [Components in depth]()
|
||||
- [Lifecycles]()
|
||||
- [The Context object]()
|
||||
- [Optional Prop fields]()
|
42
docs/guide/src/components/index.md
Normal file
42
docs/guide/src/components/index.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Introduction to Components
|
||||
|
||||
In the previous chapter, we learned about Elements and how they can be composed to create a basic user interface. Now, we'll learn how to group Elements together to form Components. We'll cover:
|
||||
|
||||
- What makes a Component
|
||||
- How to model a component and its properties in Dioxus
|
||||
- How to "think declaratively"
|
||||
|
||||
## What is a component?
|
||||
|
||||
In short, a component is a special function that takes input properties and outputs an Element. Much like a function encapsulates some specific computation task, a Component encapsulates some specific rendering task – typically, rendering an isolated part of the user interface.
|
||||
|
||||
### Real-world example
|
||||
|
||||
Let's use a Reddit post as an example:
|
||||
|
||||
![Reddit Post](../images/reddit_post.png)
|
||||
|
||||
If we look at the layout of the component, we notice quite a few buttons and pieces of functionality:
|
||||
|
||||
- Upvote/Downvote
|
||||
- View comments
|
||||
- Share
|
||||
- Save
|
||||
- Hide
|
||||
- Give award
|
||||
- Report
|
||||
- Crosspost
|
||||
- Filter by site
|
||||
- View article
|
||||
- Visit user
|
||||
|
||||
If we included all this functionality in one `rsx!` call it would be huge! Instead, let's break the post down into Components:
|
||||
|
||||
![Post as Component](../images/reddit_post_components.png)
|
||||
|
||||
- **VoteButton**: Upvote/Downvote
|
||||
- **TitleCard**: Title, Filter-By-Url
|
||||
- **MetaCard**: Original Poster, Time Submitted
|
||||
- **ActionCard**: View comments, Share, Save, Hide, Give award, Report, Crosspost
|
||||
|
||||
In this chapter, we'll learn how to define these components.
|
200
docs/guide/src/components/propsmacro.md
Normal file
200
docs/guide/src/components/propsmacro.md
Normal file
|
@ -0,0 +1,200 @@
|
|||
# Component Properties
|
||||
Dioxus components are functions that accept Props as input and output an Element. In fact, the `App` function you saw in the previous chapter was a component with no Props! Most components, however, will need to take some Props to render something useful – so, in this section, we'll learn about props:
|
||||
|
||||
- Deriving the Props trait
|
||||
- Memoization through PartialEq
|
||||
- Optional fields on props
|
||||
- The inline_props macro
|
||||
|
||||
## Props
|
||||
The input of your Component must be passed in a single struct, which must implement the `Props` trait. We can derive this trait automatically with `#[derive(Props)]`.
|
||||
|
||||
> Dioxus `Props` 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.
|
||||
|
||||
There are 2 flavors of Props: owned and borrowed.
|
||||
|
||||
- All Owned Props must implement `PartialEq`
|
||||
- Borrowed props [borrow](https://doc.rust-lang.org/beta/rust-by-example/scope/borrow.html) values from the parent Component
|
||||
|
||||
### Owned Props
|
||||
|
||||
Owned Props are very simple – they don't borrow anything. Example:
|
||||
```rust
|
||||
// Remember: owned props must implement PartialEq!
|
||||
#[derive(PartialEq, Props)]
|
||||
struct VoteButtonProps {
|
||||
score: i32
|
||||
}
|
||||
|
||||
fn VoteButton(cx: Scope<VoteButtonProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
div {
|
||||
div { "+" }
|
||||
div { "{cx.props.score}"}
|
||||
div { "-" }
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Now, we can use the VoteButton Component like we would use a regular HTML element:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! (
|
||||
VoteButton { score: 42 }
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
And we can see that the Component indeed gets rendered:
|
||||
|
||||
![Screenshot of running app. Text: "+ \ 42 \ -"](component_example_votes.png)
|
||||
|
||||
> The simplest Owned Props you can have is `()` - or no value at all. This is what the `App` Component takes as props. `Scope` accepts a generic for the Props which defaults to `()`.
|
||||
>
|
||||
> ```rust
|
||||
>// this scope
|
||||
>Scope<()>
|
||||
>
|
||||
>// is the same as this scope
|
||||
>Scope
|
||||
>```
|
||||
|
||||
### Borrowed Props
|
||||
|
||||
Owning props works well if your props are easy to copy around - like a single number. But what if we need to pass a larger data type, like a String from an `App` Component to a `TitleCard` subcomponent? A naive solution might be to [`.clone()`](https://doc.rust-lang.org/std/clone/trait.Clone.html) the String, creating a copy of it for the subcomponent – but this would be inefficient, especially for larger Strings.
|
||||
|
||||
Rust allows for something more efficient – borrowing the String as a `&str`. Instead of creating a copy, this will give us a reference to the original String – this is what Borrowed Props are for!
|
||||
|
||||
However, if we create a reference a String, Rust will require us to show that the String will not go away while we're using the reference. Otherwise, if we referenced something that doesn't exist, Bad Things could happen. To prevent this, Rust asks us to define a lifetime for the reference:
|
||||
|
||||
```rust
|
||||
#[derive(Props)]
|
||||
struct TitleCardProps<'a> {
|
||||
title: &'a str,
|
||||
}
|
||||
|
||||
fn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{cx.props.title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
This lifetime `'a` tells the compiler that as long as `title` exists, the String it was created from must also exist. Dioxus will happily accept such a component – we can now render it alongside our VoteButton!
|
||||
|
||||
```rust
|
||||
fn App(cx: Scope) -> Element {
|
||||
// For the sake of an example, we create the &str here.
|
||||
// But you might as well borrow it from an owned String type.
|
||||
let hello = "Hello Dioxus!";
|
||||
|
||||
cx.render(rsx! (
|
||||
VoteButton { score: 42 },
|
||||
TitleCard { title: hello }
|
||||
))
|
||||
}
|
||||
```
|
||||
![New screenshot of running app, now including a "Hello Dioxus!" heading.](component_example_title.png)
|
||||
|
||||
## Memoization
|
||||
|
||||
Dioxus uses Memoization for a more efficient user interface. 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 affect the output, then we don't need to re-render the component, saving time!
|
||||
|
||||
For example, let's say we have a component that has two children:
|
||||
|
||||
```rust
|
||||
fn Demo(cx: Scope) -> Element {
|
||||
// don't worry about these 2, we'll cover them later
|
||||
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 memoizes owned components. It uses `PartialEq` to determine if a component needs rerendering, or if it has stayed the same. This is why you must derive PartialEq!
|
||||
|
||||
> This 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.
|
||||
|
||||
Borrowed Props cannot be safely memoized. However, this is not a problem – Dioxus relies on the memoization of their parents to determine if they need to be rerendered.
|
||||
|
||||
## Optional Props
|
||||
|
||||
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 {
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
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` - alias for `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
|
||||
|
||||
So far, every Component function we've seen had a corresponding ComponentProps struct to pass in props. This was quite verbose... Wouldn't it be nice to have props as simple function arguments? Then we wouldn't need to define a Props struct, and instead of typing `cx.props.whatever`, we could just use `whatever` directly!
|
||||
|
||||
`inline_props` allows you to do just that. Instead of typing the "full" version:
|
||||
|
||||
```rust
|
||||
#[derive(Props, PartialEq)]
|
||||
struct TitleCardProps {
|
||||
title: String,
|
||||
}
|
||||
|
||||
fn TitleCard(cx: Scope<TitleCardProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{cx.props.title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
...you can define a function that accepts props as arguments. Then, just annotate it with `#[inline_props]`, and the macro will turn it into a regular Component for you:
|
||||
|
||||
```rust
|
||||
#[inline_props]
|
||||
fn TitleCard(cx: Scope, title: String) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
> While the new Component is shorter and easier to read, this macro should not be used by library authors since you have less control over Prop documentation.
|
|
@ -1,227 +0,0 @@
|
|||
# Introduction to Components
|
||||
|
||||
In the previous chapter, we learned about Elements and how they can be composed to create a basic User Interface. In this chapter, we'll learn how to group Elements together to form Components.
|
||||
|
||||
In this chapter, we'll learn:
|
||||
- What makes a Component
|
||||
- How to model a component and its properties in Dioxus
|
||||
- How to "think declaratively"
|
||||
|
||||
## What is a component?
|
||||
|
||||
In short, a component is a special function that takes input properties and outputs an Element. Typically, Components serve a single purpose: group functionality of a User Interface. Much like a function encapsulates some specific computation task, a Component encapsulates some specific rendering task.
|
||||
|
||||
### Learning through prior art
|
||||
|
||||
Let's take a look at a post on r/rust and see if we can sketch out a component representation.
|
||||
|
||||
![Reddit Post](../images/reddit_post.png)
|
||||
|
||||
This component has a bunch of important information:
|
||||
|
||||
- The score
|
||||
- The number of comments
|
||||
- How long ago it was posted
|
||||
- The url short address
|
||||
- The title
|
||||
- The username of the original poster
|
||||
|
||||
If we wanted to sketch out these requirements in Rust, we would start with a struct:
|
||||
|
||||
```rust
|
||||
struct PostData {
|
||||
score: i32,
|
||||
comment_count: u32,
|
||||
post_time: Instant,
|
||||
url: String,
|
||||
title: String,
|
||||
original_poster_name: String
|
||||
}
|
||||
```
|
||||
|
||||
If we look at the layout of the component, we notice quite a few buttons and pieces of functionality:
|
||||
|
||||
- Upvote/Downvote
|
||||
- View comments
|
||||
- Share
|
||||
- Save
|
||||
- Hide
|
||||
- Give award
|
||||
- Report
|
||||
- Crosspost
|
||||
- Filter by site
|
||||
- View article
|
||||
- Visit user
|
||||
|
||||
If we included all this functionality in one `rsx!` call it would be huge! Instead, let's break the post down into some core pieces:
|
||||
|
||||
![Post as Component](../images/reddit_post_components.png)
|
||||
|
||||
- **VoteButton**: Upvote/Downvote
|
||||
- **TitleCard**: Title, Filter-By-Url
|
||||
- **MetaCard**: Original Poster, Time Submitted
|
||||
- **ActionCard**: View comments, Share, Save, Hide, Give award, Report, Crosspost
|
||||
|
||||
### Modeling with Dioxus
|
||||
|
||||
We can start by sketching out the Element hierarchy using Dioxus. In general, our "Post" component will be comprised of the four sub-components listed above. First, let's define our `Post` component.
|
||||
|
||||
Unlike normal functions, Dioxus components must explicitly define a single struct to contain all the inputs. These are commonly called "Properties" (props). Our component will be a combination of these properties and a function to render them.
|
||||
|
||||
Our props must implement the `Props` trait and - if the component does not borrow any data - `PartialEq`. Both of these can be done automatically through derive macros:
|
||||
```rust
|
||||
#[derive(Props, PartialEq)]
|
||||
struct PostProps {
|
||||
id: Uuid,
|
||||
score: i32,
|
||||
comment_count: u32,
|
||||
post_time: Instant,
|
||||
url: String,
|
||||
title: String,
|
||||
original_poster: String
|
||||
}
|
||||
```
|
||||
|
||||
And our render function:
|
||||
```rust
|
||||
fn Post(cx: Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
div { class: "post-container"
|
||||
VoteButton {
|
||||
score: cx.props.score,
|
||||
}
|
||||
TitleCard {
|
||||
title: cx.props.title,
|
||||
url: cx.props.url,
|
||||
}
|
||||
MetaCard {
|
||||
original_poster: cx.props.original_poster,
|
||||
post_time: cx.props.post_time,
|
||||
}
|
||||
ActionCard {
|
||||
post_id: cx.props.id
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
When declaring a component in `rsx!`, we can pass in properties using the traditional Rust struct syntax. Our `Post` component is simply a collection of smaller components wrapped together in a single container.
|
||||
|
||||
Let's take a look at the `VoteButton` component. For now, we won't include any interactivity - just the rendering the score and buttons to the screen.
|
||||
|
||||
Most of your Components will look exactly like this: a Props struct and a render function. Every component must take a `Scope` generic over some `Props` and return an `Element`.
|
||||
|
||||
As covered before, we'll build our User Interface with the `rsx!` macro and HTML tags. However, with components, we must actually "render" our HTML markup. Calling `cx.render` converts our "lazy" `rsx!` structure into an `Element`.
|
||||
|
||||
```rust
|
||||
#[derive(PartialEq, Props)]
|
||||
struct VoteButtonProps {
|
||||
score: i32
|
||||
}
|
||||
|
||||
fn VoteButton(cx: Scope<VoteButtonProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
div { class: "votebutton"
|
||||
div { class: "arrow up" }
|
||||
div { class: "score", "{cx.props.score}"}
|
||||
div { class: "arrow down" }
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Borrowing
|
||||
|
||||
You can avoid clones by using borrowed component syntax. For example, let's say we passed the `TitleCard` title as an `&str` instead of `String`. In JavaScript, the string would be copied by reference - none of the contents would be copied, but rather the reference to the string's contents are copied. In Rust, this would be similar to calling `clone` on `Rc<str>`.
|
||||
|
||||
Because we're working in Rust, we can choose to either use `Rc<str>`, clone `Title` on every re-render of `Post`, or simply borrow it. In most cases, you'll just want to let `Title` be cloned.
|
||||
|
||||
To enable borrowed values for your component, we need to add a lifetime to let the Rust compiler know that the output `Element` borrows from the component's props.
|
||||
|
||||
```rust
|
||||
#[derive(Props)]
|
||||
struct TitleCardProps<'a> {
|
||||
title: &'a str,
|
||||
}
|
||||
|
||||
fn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{cx.props.title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
For users of React: Dioxus knows *not* to memoize components that borrow property fields. By default, every component in Dioxus is memoized. This can be disabled by the presence of a non-`'static` borrow.
|
||||
|
||||
This means that during the render process, a newer version of `TitleCardProps` will never be compared with a previous version, saving some clock cycles.
|
||||
|
||||
## 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.
|
||||
|
||||
## The `Scope` object
|
||||
|
||||
Though very similar to React, Dioxus is different in a few ways. Most notably, React components will not have a `Scope` parameter in the component declaration.
|
||||
|
||||
Have you ever wondered how the `useState()` call works in React without a `this` object to actually store the state?
|
||||
|
||||
React uses global variables to store this information. Global mutable variables must be carefully managed and are broadly discouraged in Rust programs.
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
let [state, set_state] = useState(10);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Because Dioxus needs to work with the rules of Rust it uses the `Scope` object to maintain some internal bookkeeping. That's what the `Scope` object is: a place for the component to store state, manage listeners, and allocate elements. Advanced users of Dioxus will want to learn how to properly leverage the `Scope` object to build robust and performant extensions for Dioxus.
|
||||
|
||||
```rust
|
||||
fn Post(cx: Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!("hello"))
|
||||
}
|
||||
```
|
||||
## Moving forward
|
||||
|
||||
Next chapter, we'll talk about composing Elements and Components across files to build a larger Dioxus App.
|
||||
|
||||
For more references on components, make sure to check out:
|
||||
|
||||
- [Components in depth]()
|
||||
- [Lifecycles]()
|
||||
- [The Scope object]()
|
||||
- [Optional Prop fields]()
|
||||
|
|
@ -130,7 +130,7 @@ Remember: keys let Dioxus uniquely identify an item among its siblings. A well-c
|
|||
|
||||
You might be tempted to use an item's index in the array as its key. In fact, that’s what Dioxus will use if you don’t specify a key at all. This is only acceptable if you can guarantee that the list is constant – i.e., no re-ordering, additions or deletions. In all other cases, do not use the index for the key – it will lead to the performance problems described above.
|
||||
|
||||
Note that if you pass the key to a [custom component](./components.md) you've made, it won't receive the key as a prop. It’s only used as a hint by Dioxus itself. If your component needs an ID, you have to pass it as a separate prop:
|
||||
Note that if you pass the key to a [custom component](../components/index.md) you've made, it won't receive the key as a prop. It’s only used as a hint by Dioxus itself. If your component needs an ID, you have to pass it as a separate prop:
|
||||
```rust
|
||||
Post { key: "{key}", id: "{key}" }
|
||||
```
|
||||
|
|
|
@ -1,192 +0,0 @@
|
|||
# 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.
|
|
@ -80,7 +80,7 @@ This pattern might seem strange at first, but it can be a significant upgrade ov
|
|||
## Rules of hooks
|
||||
|
||||
Hooks are sensitive to how they are used. To use hooks, you must abide by the
|
||||
"rules of hooks" (borrowed from react)](https://reactjs.org/docs/hooks-rules.html):
|
||||
"rules of hooks" ([borrowed from react](https://reactjs.org/docs/hooks-rules.html)):
|
||||
|
||||
- Functions with "use_" should not be called in callbacks
|
||||
- Functions with "use_" should not be called out of order
|
||||
|
@ -245,19 +245,10 @@ fn example(cx: Scope) -> Element {
|
|||
|
||||
By default, we bundle a handful of hooks in the Dioxus-Hooks package. Feel free to click on each hook to view its definition and associated documentation.
|
||||
|
||||
- [use_state](https://docs.rs/dioxus_hooks/use_state) - store state with ergonomic updates
|
||||
- [use_ref](https://docs.rs/dioxus_hooks/use_ref) - store non-clone state with a refcell
|
||||
- [use_future](https://docs.rs/dioxus_hooks/use_future) - store a future to be polled after initialization
|
||||
- [use_coroutine](https://docs.rs/dioxus_hooks/use_coroutine) - store a future that can be stopped/started/communicated with
|
||||
- [use_noderef](https://docs.rs/dioxus_hooks/use_noderef) - store a handle to the native element
|
||||
- [use_callback](https://docs.rs/dioxus_hooks/use_callback) - store a callback that implements PartialEq for memoization
|
||||
- [use_provide_context](https://docs.rs/dioxus_hooks/use_provide_context) - expose state to descendent components
|
||||
- [use_context](https://docs.rs/dioxus_hooks/use_context) - consume state provided by `use_provide_context`
|
||||
|
||||
For a more in-depth guide to building new hooks, checkout out the advanced hook building guide in the reference.
|
||||
|
||||
## Wrapping up
|
||||
|
||||
In this chapter, we learned about the mechanics and intricacies of storing state inside a component.
|
||||
|
||||
In the next chapter, we'll cover event listeners in similar depth, and how to combine the two to build interactive components.
|
||||
- [use_state](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_state.html) - store state with ergonomic updates
|
||||
- [use_ref](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_ref.html) - store non-clone state with a refcell
|
||||
- [use_future](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_future.html) - store a future to be polled after initialization
|
||||
- [use_coroutine](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_coroutine.html) - store a future that can be stopped/started/communicated with
|
||||
- [use_context_provider](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) - expose state to descendent components
|
||||
- [use_context](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) - consume state provided by `use_provide_context`
|
||||
- [use_suspense](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_suspense.html)
|
||||
|
|
|
@ -2,23 +2,13 @@
|
|||
|
||||
Most components you will write in Dioxus will need to store state somehow. For local state, we provide two very convenient hooks:
|
||||
|
||||
- `use_state`
|
||||
- `use_ref`
|
||||
- [use_state](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_state.html)
|
||||
- [use_ref](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_ref.html)
|
||||
|
||||
Both of these hooks are extremely powerful and flexible, so we've dedicated this section to understanding them properly.
|
||||
|
||||
> These two hooks are not the only way to store state. You can always build your own hooks!
|
||||
|
||||
## Note on Hooks
|
||||
|
||||
If you're struggling with errors due to usage in hooks, make sure you're following the rules of hooks:
|
||||
|
||||
- Functions with "use_" should not be called in callbacks
|
||||
- Functions with "use_" should not be called out of order
|
||||
- Functions with "use_" should not be called in loops or conditionals
|
||||
|
||||
A large majority of issues that seem to be "wrong with Dioxus" are actually just a misuse of hooks.
|
||||
|
||||
## `use_state`
|
||||
|
||||
The `use_state` hook is very similar to its React counterpart. When we use it, we get two values:
|
||||
|
@ -84,7 +74,7 @@ cx.spawn({
|
|||
|
||||
You might've noticed a fundamental limitation to `use_state`: to modify the value in-place, it must be cheaply cloneable. But what if your type is not cheap to clone?
|
||||
|
||||
In these cases, you should reach for `use_ref` which is essentially just a glorified `Rc<RefCell<T>>` (typical Rust UI shenanigans).
|
||||
In these cases, you should reach for `use_ref` which is essentially just a glorified `Rc<RefCell<T>>` (Rust [smart pointers](https://doc.rust-lang.org/book/ch15-04-rc.html)).
|
||||
|
||||
This provides us some runtime locks around our data, trading reliability for performance. For most cases though, you will find it hard to make `use_ref` crash.
|
||||
|
||||
|
@ -117,7 +107,7 @@ names.write().push("Tiger");
|
|||
If you don't want to re-render the component when names is updated, then we can use the `write_silent` method:
|
||||
|
||||
```rust
|
||||
names.write().push("Transmogrifier");
|
||||
names.write_silent().push("Transmogrifier");
|
||||
```
|
||||
|
||||
Again, like `UseState`, the `UseRef` handle is clonable into async contexts:
|
||||
|
@ -135,8 +125,3 @@ cx.spawn({
|
|||
}
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
## Wrapping up
|
||||
|
||||
These two hooks are extremely powerful at storing state.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Adding Interactivity
|
||||
|
||||
So far, we've learned how to describe the structure and properties of our user interfaces. Unfortunately, they're static and quite a bit uninteresting. In this chapter, we're going to learn how to add interactivity through events, state, and tasks.
|
||||
So far, we've learned how to describe the structure and properties of our user interfaces. Unfortunately, they're static and quite uninteresting. In this chapter, we're going to learn how to add interactivity through events, state, and tasks.
|
||||
|
||||
## Primer on interactivity
|
||||
|
||||
|
@ -44,9 +44,9 @@ fn App(cx: Scope<PostProps>) -> Element {
|
|||
}
|
||||
```
|
||||
|
||||
State in Dioxus follows a pattern called "one-way-data-flow." As your components create new components as their children, your app's structure will eventually grow into a tree where state gets passed down from the root component into "leaves" of the tree.
|
||||
State in Dioxus follows a pattern called "one-way data-flow." As your components create new components as their children, your app's structure will eventually grow into a tree where state gets passed down from the root component into "leaves" of the tree.
|
||||
|
||||
You've probably seen the tree of UI components represented using an directed-acyclic-graph:
|
||||
You've probably seen the tree of UI components represented using a directed acyclic graph:
|
||||
|
||||
![image](../images/component_tree.png)
|
||||
|
||||
|
@ -173,11 +173,37 @@ With these building blocks, we can craft new hooks similar to `use_state` that l
|
|||
|
||||
In general, Dioxus should be plenty fast for most use cases. However, there are some rules you should consider following to ensure your apps are quick.
|
||||
|
||||
- 1) **Don't call set—state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates or enter an infinite loop.
|
||||
- 1) **Don't call set_state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates or enter an infinite loop.
|
||||
- 2) **Break your state apart into smaller sections.** Hooks are explicitly designed to "unshackle" your state from the typical model-view-controller paradigm, making it easy to reuse useful bits of code with a single function.
|
||||
- 3) **Move local state down**. Dioxus will need to re-check child components of your app if the root component is constantly being updated. You'll get best results if rapidly-changing state does not cause major re-renders.
|
||||
|
||||
<!-- todo: link when the section exists
|
||||
Don't worry - Dioxus is fast. But, if your app needs *extreme performance*, then take a look at the `Performance Tuning` in the `Advanced Guides` book.
|
||||
-->
|
||||
|
||||
## The `Scope` object
|
||||
|
||||
Though very similar to React, Dioxus is different in a few ways. Most notably, React components will not have a `Scope` parameter in the component declaration.
|
||||
|
||||
Have you ever wondered how the `useState()` call works in React without a `this` object to actually store the state?
|
||||
|
||||
```javascript
|
||||
// in React:
|
||||
function Component(props) {
|
||||
// This state persists between component renders, but where does it live?
|
||||
let [state, set_state] = useState(10);
|
||||
}
|
||||
```
|
||||
|
||||
React uses global variables to store this information. However, global mutable variables must be carefully managed and are broadly discouraged in Rust programs. Because Dioxus needs to work with the rules of Rust it uses the `Scope` rather than a global state object to maintain some internal bookkeeping.
|
||||
|
||||
That's what the `Scope` object is: a place for the Component to store state, manage listeners, and allocate elements. Advanced users of Dioxus will want to learn how to properly leverage the `Scope` object to build robust and performant extensions for Dioxus.
|
||||
|
||||
```rust
|
||||
fn Post(cx: Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!("hello"))
|
||||
}
|
||||
```
|
||||
|
||||
## Moving On
|
||||
|
||||
|
|
Loading…
Reference in a new issue