Merge branch 'master' into jk/docs
This commit is contained in:
commit
0592cdb135
111 files changed
+3939
-2077
No files matched your search
@@ -9,7 +9,7 @@ In this chapter, you'll learn about:
|
||||
|
||||
## The use case
|
||||
|
||||
Let's say you're building a user interface and want to make some part of it clickable to another website. You would normally start with the HTML `<a>` tag, like so:
|
||||
Let's say you're building a user interface and want to make some part of it a clickable link to another website. You would normally start with the HTML `<a>` tag, like so:
|
||||
|
||||
```rust
|
||||
rsx!(
|
||||
@@ -98,7 +98,7 @@ struct ClickableProps<'a> {
|
||||
children: Element<'a>
|
||||
}
|
||||
|
||||
fn clickable(cx: Scope<ClickableProps>) -> Element {
|
||||
fn Clickable(cx: Scope<ClickableProps>) -> Element {
|
||||
cx.render(rsx!(
|
||||
a {
|
||||
href: "{cx.props.href}",
|
||||
@@ -162,7 +162,7 @@ struct ClickableProps<'a> {
|
||||
|
||||
fn clickable(cx: Scope<ClickableProps>) -> Element {
|
||||
cx.render(rsx!(
|
||||
a {
|
||||
a {
|
||||
..cx.props.attributes,
|
||||
"Any link, anywhere"
|
||||
}
|
||||
@@ -186,7 +186,7 @@ struct ClickableProps<'a> {
|
||||
|
||||
fn clickable(cx: Scope<ClickableProps>) -> Element {
|
||||
cx.render(rsx!(
|
||||
a {
|
||||
a {
|
||||
onclick: move |evt| cx.props.onclick.call(evt)
|
||||
}
|
||||
))
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
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
|
||||
- What makes a Component
|
||||
- How to model a component and its properties in Dioxus
|
||||
- How to "think declaratively"
|
||||
|
||||
## What is a component?
|
||||
@@ -39,7 +39,7 @@ struct PostData {
|
||||
}
|
||||
```
|
||||
|
||||
If we look at the layout of the component, we notice quite a few buttons and functionality:
|
||||
If we look at the layout of the component, we notice quite a few buttons and pieces of functionality:
|
||||
|
||||
- Upvote/Downvote
|
||||
- View comments
|
||||
@@ -51,9 +51,9 @@ If we look at the layout of the component, we notice quite a few buttons and fun
|
||||
- Crosspost
|
||||
- Filter by site
|
||||
- View article
|
||||
- Visit user
|
||||
- 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:
|
||||
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:
|
||||
|
||||

|
||||
|
||||
@@ -112,7 +112,7 @@ Let's take a look at the `VoteButton` component. For now, we won't include any i
|
||||
|
||||
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`.
|
||||
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)]
|
||||
@@ -133,9 +133,9 @@ fn VoteButton(cx: Scope<VoteButtonProps>) -> Element {
|
||||
|
||||
## Borrowing
|
||||
|
||||
You can avoid clones 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>`.
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -149,18 +149,54 @@ 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.
|
||||
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?
|
||||
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.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Now that we have a "logged_in" flag accessible in our props, we can render two d
|
||||
|
||||
```rust
|
||||
fn App(cx: Scope<AppProps>) -> Element {
|
||||
if props.logged_in {
|
||||
if cx.props.logged_in {
|
||||
cx.render(rsx!{
|
||||
DashboardScreen {}
|
||||
})
|
||||
@@ -39,7 +39,7 @@ fn App(cx: Scope<AppProps>) -> Element {
|
||||
}
|
||||
```
|
||||
|
||||
When the user is logged in, then this component will return the DashboardScreen. Else, the component will render the LoginScreen.
|
||||
When the user is logged in, then this component will return the DashboardScreen. If they're not logged in, the component will render the LoginScreen.
|
||||
|
||||
## Using match statements
|
||||
|
||||
|
||||
@@ -25,27 +25,27 @@ fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(Scope) -> Element {}
|
||||
fn App(Scope) -> Element {}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct PostProps{}
|
||||
fn Post(Scope<PostProps>) -> Element {}
|
||||
fn Post(Scope<PostProps>) -> Element {}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct VoteButtonsProps {}
|
||||
fn VoteButtons(Scope<VoteButtonsProps>) -> Element {}
|
||||
fn VoteButtons(Scope<VoteButtonsProps>) -> Element {}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct TitleCardProps {}
|
||||
fn TitleCard(Scope<TitleCardProps>) -> Element {}
|
||||
fn TitleCard(Scope<TitleCardProps>) -> Element {}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct MetaCardProps {}
|
||||
fn MetaCard(Scope<MetaCardProps>) -> Element {}
|
||||
fn MetaCard(Scope<MetaCardProps>) -> Element {}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct ActionCardProps {}
|
||||
fn ActionCard(Scope<ActionCardProps>) -> Element {}
|
||||
fn ActionCard(Scope<ActionCardProps>) -> Element {}
|
||||
```
|
||||
|
||||
That's a lot of components for one file! We've successfully refactored our app into components, but we should probably start breaking it up into a file for each component.
|
||||
@@ -61,7 +61,7 @@ use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct ActionCardProps {}
|
||||
fn ActionCard(Scope<ActionCardProps>) -> Element {}
|
||||
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.
|
||||
@@ -104,10 +104,10 @@ fn App(Scope) -> Element {
|
||||
original_poster: "me".to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you tried to build this app right now, you'll get an error message saying that `Post is private, trying changing it to public`. This is because we haven't properly exported our component! To fix this, we need to make sure both the Props and Component are declared as "public":
|
||||
If you tried to build this app right now, you'll get an error message saying that `Post is private, try changing it to public`. This is because we haven't properly exported our component! To fix this, we need to make sure both the Props and Component are declared as "public":
|
||||
|
||||
```rust
|
||||
// src/post/mod.rs
|
||||
@@ -116,7 +116,7 @@ use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
pub struct PostProps {}
|
||||
pub fn Post(Scope<PostProps>) -> Element {}
|
||||
pub fn Post(Scope<PostProps>) -> Element {}
|
||||
```
|
||||
|
||||
While we're here, we also need to make sure each of our subcomponents are included as modules and exported.
|
||||
@@ -203,7 +203,7 @@ fn App(Scope) -> Element {
|
||||
original_poster: "me".to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
pub struct VoteButtonsProps {}
|
||||
pub fn VoteButtons(Scope<VoteButtonsProps>) -> Element {}
|
||||
pub fn VoteButtons(Scope<VoteButtonsProps>) -> Element {}
|
||||
```
|
||||
|
||||
```rust
|
||||
@@ -264,7 +264,7 @@ use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
pub struct TitleCardProps {}
|
||||
pub fn TitleCard(Scope<TitleCardProps>) -> Element {}
|
||||
pub fn TitleCard(Scope<TitleCardProps>) -> Element {}
|
||||
```
|
||||
|
||||
```rust
|
||||
@@ -273,7 +273,7 @@ use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
pub struct MetaCardProps {}
|
||||
pub fn MetaCard(Scope<MetaCardProps>) -> Element {}
|
||||
pub fn MetaCard(Scope<MetaCardProps>) -> Element {}
|
||||
```
|
||||
|
||||
```rust
|
||||
@@ -282,7 +282,7 @@ use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
pub struct ActionCardProps {}
|
||||
pub fn ActionCard(Scope<ActionCardProps>) -> Element {}
|
||||
pub fn ActionCard(Scope<ActionCardProps>) -> Element {}
|
||||
```
|
||||
|
||||
## Moving forward
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Core Topics
|
||||
|
||||
In this chapter, we'll cover some core topics on how Dioxus works and how to best leverage the features to build a beautiful, reactive app.
|
||||
In this chapter, we'll cover some core topics about how Dioxus works and how to best leverage the features to build a beautiful, reactive app.
|
||||
|
||||
At a very high level, Dioxus is simply a Rust framework for _declaring_ user interfaces and _reacting_ to changes.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Conditional Lists and Keys
|
||||
|
||||
You will often want to display multiple similar components from a collection of data.
|
||||
You will often want to display multiple similar components from a collection of data.
|
||||
|
||||
In this chapter, you will learn:
|
||||
|
||||
@@ -22,9 +22,9 @@ rsx!(
|
||||
)
|
||||
```
|
||||
|
||||
Instead, we need to transform the list of data into a list of Elements.
|
||||
Instead, we need to transform the list of data into a list of Elements.
|
||||
|
||||
For convenience, `rsx!` supports any type in curly braces that implements the `IntoVnodeList` trait. Conveniently, every iterator that returns something that can be rendered as an Element also implements `IntoVnodeList`.
|
||||
For convenience, `rsx!` supports any type in curly braces that implements the `IntoVnodeList` trait. Conveniently, every iterator that returns something that can be rendered as an Element also implements `IntoVnodeList`.
|
||||
|
||||
As a simple example, let's render a list of names. First, start with our input data:
|
||||
|
||||
@@ -70,11 +70,46 @@ The HTML-rendered version of this list would follow what you would expect:
|
||||
</ul>
|
||||
```
|
||||
|
||||
### Rendering our posts with a PostList component
|
||||
|
||||
Let's start by modeling this problem with a component and some properties.
|
||||
|
||||
For this example, we're going to use the borrowed component syntax since we probably have a large list of posts that we don't want to clone every time we render the Post List.
|
||||
|
||||
```rust
|
||||
#[derive(Props, PartialEq)]
|
||||
struct PostListProps<'a> {
|
||||
posts: &'a [PostData]
|
||||
}
|
||||
```
|
||||
Next, we're going to define our component:
|
||||
|
||||
```rust
|
||||
fn App(cx: Scope<PostList>) -> Element {
|
||||
// First, we create a new iterator by mapping the post array
|
||||
let posts = cx.props.posts.iter().map(|post| rsx!{
|
||||
Post {
|
||||
title: post.title,
|
||||
age: post.age,
|
||||
original_poster: post.original_poster
|
||||
}
|
||||
});
|
||||
|
||||
// Finally, we render the post list inside of a container
|
||||
cx.render(rsx!{
|
||||
ul { class: "post-list"
|
||||
{posts}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Filtering Iterators
|
||||
|
||||
Rust's iterators are extremely powerful, especially when used for filtering tasks. When building user interfaces, you might want to display a list of items filtered by some arbitrary check.
|
||||
|
||||
As a very simple example, let's set up a filter where we only list names that begin with the letter "J".
|
||||
As a very simple example, let's set up a filter where we only list names that begin with the letter "J".
|
||||
|
||||
Let's make our list of names:
|
||||
|
||||
@@ -93,7 +128,7 @@ let name_list = names
|
||||
|
||||
Rust's iterators provide us tons of functionality and are significantly easier to work with than JavaScript's map/filter/reduce.
|
||||
|
||||
For keen Rustaceans: notice how we don't actually call `collect` on the name list. If we `collected` our filtered list into new Vec, then we would need to make an allocation to store these new elements. Instead, we create an entirely new _lazy_ iterator which will then be consumed by Dioxus in the `render` call.
|
||||
For keen Rustaceans: notice how we don't actually call `collect` on the name list. If we `collected` our filtered list into new Vec, then we would need to make an allocation to store these new elements. Instead, we create an entirely new _lazy_ iterator which will then be consumed by Dioxus in the `render` call.
|
||||
|
||||
The `render` method is extraordinarily efficient, so it's best practice to let it do most of the allocations for us.
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ Alternatively, `&str` can be included directly, though it must be inside of squa
|
||||
rsx!( "Hello ", [if enabled { "Jack" } else { "Bob" }] )
|
||||
```
|
||||
|
||||
This is different from React's way of generating arbitrary markup but fits within idiomatic Rust.
|
||||
This is different from React's way of generating arbitrary markup but fits within idiomatic Rust.
|
||||
|
||||
Typically, with Dioxus, you'll just want to compute your substrings outside of the `rsx!` call and leverage the f-string formatting:
|
||||
|
||||
@@ -138,11 +138,11 @@ rsx!(
|
||||
All element attributes must occur *before* child elements. The `rsx!` macro will throw an error if your child elements come before any of your attributes. If you don't see the error, try editing your Rust-Analyzer IDE setting to ignore macro-errors. This is a temporary workaround because Rust-Analyzer currently throws *two* errors instead of just the one we care about.
|
||||
|
||||
```rust
|
||||
// settings.json
|
||||
// settings.json
|
||||
{
|
||||
"rust-analyzer.diagnostics.disabled": [
|
||||
"macro-error"
|
||||
],
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
@@ -166,7 +166,7 @@ This chapter just scratches the surface on how Elements can be defined.
|
||||
|
||||
We learned:
|
||||
- Elements are the basic building blocks of User Interfaces
|
||||
- Elements can contain other elements
|
||||
- Elements can contain other elements
|
||||
- Elements can either be a named container or text
|
||||
- Some Elements have properties that the renderer can use to draw the UI to the screen
|
||||
|
||||
|
||||
Reference in new issue
Block a user