Merge branch 'master' into jk/docs

This commit is contained in:
Jonathan Kelley committed 2022-01-28 00:21:42 -05:00
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)
}
))
+47 -11
View File
@@ -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:
![Post as Component](../images/reddit_post_components.png)
@@ -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
+15 -15
View File
@@ -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 -1
View File
@@ -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.
+40 -5
View File
@@ -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.
+4 -4
View File
@@ -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