docs: even more docs

This commit is contained in:
Jonathan Kelley committed 2022-03-06 20:37:57 -05:00
1 parent a4ab2d9de0
commit 0f87ebb277
25 files changed
+986 -174

No files matched your search

+1
View File
@@ -0,0 +1 @@
# Channels
+61
View File
@@ -0,0 +1,61 @@
# Fanning Out
One of the most reliable state management patterns in large Dioxus apps is `fan-out`. The fan-out pattern is the ideal way to structure your app to maximize code reuse, testability, and deterministic rendering.
## The structure
With `fan-out`, our individual components at "leaf" position of our VirtualDom are "pure", making them easily reusable, testable, and deterministic. For instance, the "title" bar of our app might be a fairly complicated component.
```rust
#[derive(Props, PartialEq)]
struct TitlebarProps {
title: String,
subtitle: String,
}
fn Titlebar(cx: Scope<TitlebarProps>) -> Element {
cx.render(rsx!{
div {
class: "titlebar"
h1 { "{cx.props.title}" }
h1 { "{cx.props.subtitle}" }
}
})
}
```
If we used global state like use_context or fermi, we might be tempted to inject our `use_read` directly into the component.
```rust
fn Titlebar(cx: Scope<TitlebarProps>) -> Element {
let title = use_read(&cx, TITLE);
let subtitle = use_read(&cx, SUBTITLE);
cx.render(rsx!{/* ui */})
}
```
For many apps - this is a fine pattern, especially if the component is a one-off. However, if we want to reuse the component outside of this app, then we'll start to run into issues where our contexts are unavailable.
## Fanning Out
To enable our titlebar component to be used across apps, we want to lift our atoms upwards and out of the Titlebar component. We would organize a bunch of other components in this section of the app to share some of the same state.
```rust
fn DocsiteTitlesection(cx: Scope) {
let title = use_read(&cx, TITLE);
let subtitle = use_read(&cx, SUBTITLE);
let username = use_read(&cx, USERNAME);
let points = use_read(&cx, POINTS);
cx.render(rsx!{
TitleBar { title: title, subtitle: subtitle }
UserBar { username: username, points: points }
})
}
```
This particular wrapper component unfortunately cannot be reused across apps. However, because it simply wraps other real elements, it doesn't have to. We are free to reuse our TitleBar and UserBar components across apps with ease. We also know that this particular component is plenty performant because the wrapper doesn't have any props and is always memoized. The only times this component re-renders is when any of the atoms change.
This is the beauty of Dioxus - we always know where our components are likely to be re-rendered. Our wrapper components can easily prevent any large re-renders by simply memoizing their components. This system might not be as elegant or precise as signal systems found in libraries like Sycamore or SolidJS, but it is quite ergonomic.
+110
View File
@@ -0,0 +1,110 @@
# Fermi
After having covered local and global state, you're definitely ready to start building some complex Dioxus apps. Before you get too far, check out the Fermi crate. Fermi makes it dead-easy to manage global state and scales to the largest of apps.
## What is Fermi for?
If you're building an app that has scaled past a few small components, then it'll be worth sketching out and organizing your app's state. Fermi makes it easy to transition from a simple app that relies on `use_state` to an app with dozens of components.
> To put it simply - Fermi is the ideal crate for managing global state in your Dioxus app.
## How do I use it?
To add fermi to your project, simply add the "fermi" feature to your Dioxus dependency.
```toml
[dependencies]
dioxus = { version = "0.2", features = ["desktop", "fermi"] }
```
Fermi is built on the concept of "atoms" of global state. Instead of bundling all our state together in a single mega struct, we actually chose to implement it piece-by-piece with functions.
Each piece of global state in your app is represented by an "atom."
```rust
static TITLE: Atom<String> = |_| "Defualt Title".to_string();
```
This atom can be then used the with the `use_atom` hook as a drop-in replacement for `use_state`.
```rust
static TITLE: Atom<String> = |_| "Defualt Title".to_string();
fn Title(cx: Scope) -> Element {
let title = use_atom(&cx, TITLE);
cx.render(rsx!{
button { onclick: move |_| title.set("new title".to_string()) }
})
}
```
However, Fermi really becomes useful when we want to share the value between two different components.
```rust
static TITLE: Atom<String> = |_| "Defualt Title".to_string();
fn TitleBar(cx: Scope) -> Element {
let title = use_atom(&cx, TITLE);
rsx!{cx, button { onclick: move |_| title.set("titlebar title".to_string()) } }
}
fn TitleCard(cx: Scope) -> Element {
let title = use_atom(&cx, TITLE);
rsx!{cx, button { onclick: move |_| title.set("title card".to_string()) } }
}
```
These two components can get and set the same value!
## Use with collections
Fermi gets *really* powerful when used to manage collections of data. Under the hood, Fermi uses immutable collections and tracks reads and writes of individual keys. This makes it easy to implement things like lists and infinite posts with little performance penalty. It also makes it really easy to refactor our app and add new fields.
```rust
static NAMES: AtomRef<Uuid, String> = |builder| {};
static CHECKED: AtomRef<Uuid, bool> = |builder| {};
static CREATED: AtomRef<Uuid, Instant> = |builder| {};
```
To use these collections:
```rust
#[inline_props]
fn Todo(cx: Scope, id: Uuid) -> Element {
let name = use_atom(&cx, NAMES.select(id));
let checked = use_atom(&cx, CHECKED.select(id));
let created = use_atom(&cx, CREATED.select(id));
// or
let (name, checked, created) = use_atom(&cx, (NAMES, CHECKED, CREATED).select(id));
}
```
This particular pattern might seem strange at first - "why isn't all of our state under one struct?" - but eventually shows its worth when dealing with large amounts of data. By composing collections together, we can get get the perks of the Entity-Component-System architecture in our Dioxus apps. Performance is quite predictable here and easy to trace.
## AtomRef
Much like `use_ref` can be used to manage complex state locally, `AtomRef` can be used to manage complex global state. `AtomRef` is basically a global `Rc<RefCell<T>>` with mutation tracking.
It too serves as a basic replacement for `use_ref`:
```rust
fn Todo(cx: Scope) -> Element {
let cfg = use_atom_ref(&cx, CFG);
cfg.write().enable_option();
}
```
## Future Reading
This guide is meant just to be an overview of Fermi. This page is just a short advertisement for what we consider the best state management solution available today for Dioxus apps.
For further reading, check out the [crate itself](https://github.com/DioxusLabs/dioxus/tree/master/packages/fermi)!
+28 -17
View File
@@ -2,27 +2,38 @@
Every app you'll build with Dioxus will have some sort of state that needs to be maintained and updated as your users interact with it. However, managing state can be particular challenging at times, and is frequently the source of bugs in many GUI frameworks.
In this chapter, we'll cover the various ways to manage state, the appropriate terminology, various patterns, and then take an overview
## Terminology
In this chapter, we'll cover the various ways to manage state, the appropriate terminology, various patterns, and some problems you might run into.
> This section is currently under construction! 🏗
## The Problem
Why do people say state management is so difficult? What does it mean?
Generally, state management is the code you need to write to ensure that your app renders the *correct* content. If the user inputs a name, then you need to display the appropriate response - like alerts, validation, and disable/enable various elements on the page. Things can quickly become tricky if you need loading screens and cancellable tasks.
For the simplest of apps, all your state can enter the app from the root props. This is common in server-side rendering - we can collect all of the required state *before* rendering the content.
```rust
let all_content = get_all_content().await;
let output = dioxus::ssr::render_lazy(rsx!{
div {
RenderContent { content: all_content }
}
});
```
With this incredibly simple setup, it is highly unlikely that you'll have rendering bugs. There simply is barely any state to manage.
However, most of your apps will store state inside of the Dioxus VirtualDom - either through local state or global state.
## Your options
To deal with complexity, you have a couple of options:
## Important hook: `use_state`
- Refactor state out of shared state and into reusable components and hooks.
- Lift state upwards to be spread across multiple components (fan out).
- Use the Context API to share state globally.
- Use a dedicated state management solution like Fermi.
## Important hook: `use_ref`
## `provide_context` and `consume_context`
## Terminology
+59 -1
View File
@@ -1,4 +1,62 @@
# Local State
The first step to dealing with complexity in your app is to refactor your state to be purely local. This encourages good code reuse and prevents leakage of abstractions across component boundaries.
> This section is currently under construction! 🏗
## What it looks like
Let's say we're managing the state for a list of Todos. Whenever we edit the todo, we want the list to update. We might've started building our app but putting everything into a single `use_ref`.
```rust
struct Todo {
contents: String,
is_hovered: bool,
is_editing: bool,
}
let todos = use_ref(&cx, || vec![Todo::new()]);
cx.render(rsx!{
ul {
todos.read().iter().enumerate().map(|(id, todo)| rsx!{
li {
h1 { "{todo.contents}" }
onhover: move |_| *todos.write()[id].is_hovered = true;
}
})
}
})
```
As shown above, whenever the todo is hovered, we want to set its state:
```rust
todos.write()[0].is_hovered = true;
```
As the amount of interactions goes up, so does the complexity of our state. Should the "hover" state really be baked into our data model?
Instead, let's refactor our Todo component to handle its own state:
```rust
#[inline_props]
fn Todo<'a>(cx: Scope, todo: &'a Todo) -> Element {
let is_hovered = use_state(&cx, || false);
cx.render(rsx!{
li {
h1 { "{todo.contents}" }
onhover: move |_| is_hovered.set(true),
}
})
}
```
Now, we can simplify our Todo data model to get rid of local UI state:
```rust
struct Todo {
contents: String,
}
```
This is not only better for encapsulation and abstraction, but it's only more performant! Whenever a Todo is hovered, we won't need to re-render *every* Todo again - only the Todo that's currently being hovered.
+86
View File
@@ -0,0 +1,86 @@
# Router
In many of your apps, you'll want to have different "scenes". For a webpage, these scenes might be the different webpages with their own content.
You could write your own scene management solution - quite simply too. However, to save you the effort, Dioxus supports a first-party solution for scene management called Dioxus Router.
## What is it?
For an app like the Dioxus landing page (https://dioxuslabs.com), we want to have different pages. A quick sketch of an app would be something like:
- Homepage
- Blog
- Example showcase
Each of these scenes is independent - we don't want to render both the homepage and blog at the same time.
This is where the router crates come in handy. To make sure we're using the router, simply add the `"router"` feature to your dioxus dependency.
```toml
[dependencies]
dioxus = { version = "0.2", features = ["desktop", "router"] }
```
## Using the router
Unlike other routers in the Rust ecosystem, our router is built declaratively. This makes it possible to compose our app layout simply by arranging components.
```rust
rsx!{
Router {
Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} }
}
}
```
Whenever we visit this app, we will get either the Home component or the Blog component rendered depending on which route we enter at. If neither of these routes match the current location, then nothing will render.
We can fix this one of two ways:
- A fallback 404 page
-
```rust
rsx!{
Router {
Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} }
Route { to: "", NotFound {} }
}
}
```
- Redirect 404 to home
```rust
rsx!{
Router {
Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} }
Redirect { from: "", to: "/home" }
}
}
```
## Links
For our app to navigate these routes, we can provide clickable elements called Links. These simply wrap `<a>` elements that, when clicked, navigate the app to the given location.
```rust
rsx!{
Link {
to: "/home",
"Go home!"
}
}
```
## More reading
This page is just meant to be a very brief overview of the router to show you that there's a powerful solution already built for a very common problem. For more information about the router, definitely check out its book or check out some of the examples.
The router has its own documentation! [Available here](https://dioxuslabs.com/router_guide/).