wip: some docs and suspense

This commit is contained in:
Jonathan Kelley committed 2021-11-23 15:53:57 -05:00
1 parent bab21a0aa1
commit 93d4b8ca7c
22 files changed
+210 -252

No files matched your search

+13 -10
View File
@@ -5,7 +5,7 @@
**Dioxus** is a framework and ecosystem for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get up-and-running with Dioxus running on the Web, Desktop, Mobile, and more.
```rust
fn App((cx, props): Component<()>) -> Element {
fn App(cx: Context, props: &()) -> Element {
let mut count = use_state(cx, || 0);
cx.render(rsx!(
@@ -16,11 +16,20 @@ fn App((cx, props): Component<()>) -> Element {
};
```
The Dioxus API and patterns closely resemble React - if this guide is lacking in any general concept or an error message is confusing, we recommend substituting "React" for "Dioxus" in your web search terms. A major goal of Dioxus is to provide a familiar toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc) - if you know React, then you already know Dioxus. If you don't know either, this guide will still help you!
In general, Dioxus and React share many functional similarities. If this guide is lacking in any general concept or an error message is confusing, React's documentation might be more helpful. We are dedicated to providing a *familiar* toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc). If you know React, then you already know Dioxus. If you don't know either, this guide will still help you!
> This is introduction book! For advanced topics, check out the [Reference Guide]() instead.
## Multiplatform
Dioxus is a *portable* toolkit, meaning the Core implementation can run anywhere with no platform-dependent linking. Unlike many other Rust frontend toolkits, Dioxus is not intrinsically linked to Web-Sys. In fact, every element and event listener can be swapped out at compile time. By default, Dioxus ships with the `Html` feature enabled which can be disabled depending on your target renderer.
Right now, we have several 1st-party renderers:
- WebSys (for WASM)
- Tao/Tokio (for Desktop apps)
- Tao/Tokio (for Mobile apps)
- SSR (for generating static markup)
### Web Support
---
@@ -39,12 +48,7 @@ Examples:
### SSR Support
---
Dioxus supports server-side rendering! In a pinch, you can literally "debug" the VirtualDom:
```rust
let dom = VirtualDom::new(App);
println!("{:?}, dom");
```
Dioxus supports server-side rendering!
For rendering statically to an `.html` file or from a WebServer, then you'll want to make sure the `ssr` feature is enabled in the `dioxus` crate and use the `dioxus::ssr` API. We don't expect the SSR API to change drastically in the future.
@@ -52,7 +56,6 @@ For rendering statically to an `.html` file or from a WebServer, then you'll wan
let contents = dioxus::ssr::render_vdom(&dom, |c| c);
```
[Jump to the getting started guide for SSR.]()
Examples:
+11 -10
View File
@@ -26,7 +26,7 @@ container.push(green_light);
container.push(yellow_light);
container.push(red_light);
container.onclick(move |_| {
container.set_onclick(move |_| {
if red_light.enabled() {
red_light.set_enabled(false);
green_light.set_enabled(true);
@@ -45,22 +45,23 @@ As the UI grows in scale, our logic to keep each element in the proper state wou
Instead, with Dioxus, we *declare* what we want our UI to look like:
```rust
let state = "red";
let mut state = use_state(cx, || "red");
rsx!(
cx.render(rsx!(
Container {
Light { color: "red", enabled: {state == "red"} }
Light { color: "yellow", enabled: {state == "yellow"} }
Light { color: "green", enabled: {state == "green"} }
onclick: |_| {
state = match state {
Light { color: "red", enabled: format_args!("{}", state == "red") }
Light { color: "yellow", enabled: format_args!("{}", state == "yellow") }
Light { color: "green", enabled: format_args!("{}", state == "green") }
onclick: move |_| {
state.set(match *state {
"green" => "yellow",
"yellow" => "red",
"red" => "green",
}
})
}
}
)
))
```
Remember: this concept is not new! Many frameworks are declarative - with React being the most popular. Declarative frameworks tend to be much more enjoyable to work with than imperative frameworks.
+5 -5
View File
@@ -84,7 +84,7 @@ struct PostProps {
And our render function:
```rust
fn Post((cx, props): Scope<PostProps>) -> Element {
fn Post(cx: Context, props: &PostProps) -> Element {
cx.render(rsx!{
div { class: "post-container"
VoteButton {
@@ -120,7 +120,7 @@ struct VoteButtonProps {
score: i32
}
fn VoteButton((cx, props): Scope<VoteButtonProps>) -> Element {
fn VoteButton(cx: Context, props: &VoteButtonProps) -> Element {
cx.render(rsx!{
div { class: "votebutton"
div { class: "arrow up" }
@@ -145,7 +145,7 @@ struct TitleCardProps<'a> {
title: &'a str,
}
fn TitleCard<'a>((cx, props): Scope<'a, TitleCardProps>) -> Element<'a> {
fn TitleCard(cx: Context, props: &TitleCardProps) -> Element {
cx.render(rsx!{
h1 { "{props.title}" }
})
@@ -174,8 +174,8 @@ function Component(props) {
Because Dioxus needs to work with the rules of Rust it uses the `Context` object to maintain some internal bookkeeping. That's what the `Context` 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 `Context` object to build robust and performant extensions for Dioxus.
```rust
fn Post((cx /* <-- our Context object*/, props): Scope<PostProps>) -> Element {
cx.render(rsx!{ })
fn Post(cx: Context, props: &PostProps) -> Element {
cx.render(rsx!("hello"))
}
```
## Moving forward
@@ -26,7 +26,7 @@ struct AppProps {
Now that we have a "logged_in" flag accessible in our props, we can render two different screens:
```rust
fn App((cx, props): Scope<AppProps>) -> Element {
fn App(cx: Context, props: &AppProps) -> Element {
if props.logged_in {
cx.render(rsx!{
DashboardScreen {}
@@ -48,7 +48,7 @@ Rust provides us algebraic datatypes: enums that can contain values. Using the `
For instance, we could run a function that returns a Result:
```rust
fn App((cx, props): Scope<()>) -> Element {
fn App(cx: Context, props: &())-> Element {
match get_name() {
Ok(name) => cx.render(rsx!( "Hello, {name}!" )),
Err(err) => cx.render(rsx!( "Sorry, I don't know your name, because an error occurred: {err}" )),
@@ -58,7 +58,7 @@ fn App((cx, props): Scope<()>) -> Element {
We can even match against values:
```rust
fn App((cx, props): Scope<()>) -> Element {
fn App(cx: Context, props: &())-> Element {
match get_name() {
"jack" => cx.render(rsx!( "Hey Jack, how's Diane?" )),
"diane" => cx.render(rsx!( "Hey Diana, how's Jack?" )),
@@ -67,12 +67,12 @@ fn App((cx, props): Scope<()>) -> Element {
}
```
Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type.
Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type. To turn our `rsx!` into Elements, we need to call `cx.render`.
To make patterns like these less verbose, the `rsx!` macro accepts an optional first argument on which it will call `render`. Our previous component can be shortened with this alternative syntax:
```rust
fn App((cx, props): Scope<()>) -> Element {
fn App(cx: Context, props: &())-> Element {
match get_name() {
"jack" => rsx!(cx, "Hey Jack, how's Diane?" ),
"diane" => rsx!(cx, "Hey Diana, how's Jack?" ),
@@ -83,13 +83,13 @@ fn App((cx, props): Scope<()>) -> Element {
This syntax even enables us to write a one-line component:
```rust
static App: Fc<()> = |cx, props| rsx!(cx, "hello world!");
static App: FC<()> = |cx, props| rsx!(cx, "hello world!");
```
Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`:
```rust
fn App((cx, props): Scope<()>) -> Element {
fn App(cx: Context, props: &())-> Element {
let greeting = match get_name() {
"jack" => rsx!("Hey Jack, how's Diane?" ),
"diane" => rsx!("Hey Diana, how's Jack?" ),
+7 -7
View File
@@ -35,7 +35,7 @@ fn main() {
When Dioxus renders your app, it will pass an immutable reference of `PostProps` to your `Post` component. Here, you can pass the state down into children.
```rust
fn App((cx, props): Scope<PostProps>) -> Element {
fn App(cx: Context, props: &PostProps) -> Element {
cx.render(rsx!{
Title { title: &props.title }
Score { score: &props.score }
@@ -63,8 +63,8 @@ Instead, you'll want to store state internally in your components and let *that*
The most common hook you'll use for storing state is `use_state`. `use_state` provides a slot for some data that allows you to read and update the value without accidentally mutating it.
```rust
fn App((cx, props): Scope<()>) -> Element {
let post = use_state(|| {
fn App(cx: Context, props: &())-> Element {
let post = use_state(cx, || {
PostData {
id: Uuid::new_v4(),
score: 10,
@@ -111,8 +111,8 @@ When responding to user-triggered events, we'll want to "listen" for an event on
For example, let's say we provide a button to generate a new post. Whenever the user clicks the button, they get a new post. To achieve this functionality, we'll want to attach a function to the `on_click` method of `button`. Whenever the button is clicked, our function will run, and we'll get new Post data to work with.
```rust
fn App((cx, props): Scope<()>) -> Element {
let post = use_state(|| PostData::new());
fn App(cx: Context, props: &())-> Element {
let post = use_state(cx, || PostData::new());
cx.render(rsx!{
button {
@@ -134,8 +134,8 @@ We can use tasks in our components to build a tiny stopwatch that ticks every se
```rust
fn App((cx, props): Scope<()>) -> Element {
let mut sec_elapsed = use_state(|| 0);
fn App(cx: Context, props: &())-> Element {
let mut sec_elapsed = use_state(cx, || 0);
cx.spawn_task(async move {
TimeoutFuture::from_ms(1000).await;
+1 -1
View File
@@ -74,7 +74,7 @@ struct PostListProps<'a> {
Next, we're going to define our component:
```rust
fn App((cx, props): Component<PostList>) -> Element {
fn App(cx: Context, props: &PostList) -> Element {
// First, we create a new iterator by mapping the post array
let posts = props.posts.iter().map(|post| rsx!{
Post {
+53
View File
@@ -1 +1,54 @@
# Suspense
Suspense in Dioxus is enabled through placeholder nodes.
just a component that renders nodes into the placeholder after the future is finished?
in react, suspense is just completely pausing diffing while a
```rust
let n = use_suspense(cx || {
cx.render(rsx!{
Suspense {
prom: fut,
callback: || {}
}
})
})
suspense () {
let value = use_state();
if first_render {
push_task({
value.set(fut.await);
});
} else {
callback(value)
}
}
let name = fetch_name().await;
function ProfileDetails() {
// Try to read user info, although it might not have loaded yet
const user = resource.read();
return <h1>{user.name}</h1>;
}
fn ProfileDteails() {
let user = resource.suspend(cx, |l| rsx!("{l}"));
// waits for the resource to be ready and updates the placeholder with the tree
let name = resource.suspend_with(cx, |val| rsx!( div { "hello" "{user}" } ));
cx.render(rsx!(
div {
{user}
{name}
}
))
}
```
+1 -1
View File
@@ -107,7 +107,7 @@ To do this, we use the familiar struct-style syntax that Rust provides. Commas a
```rust
rsx!(
div {
hidden: true,
hidden: "true",
background_color: "blue",
class: "card color-{mycolor}"
}
+15 -25
View File
@@ -1,4 +1,4 @@
# Hello World
# Hello, World desktop app
Let's put together a simple "hello world" desktop application to get acquainted with Dioxus.
@@ -92,10 +92,10 @@ use dioxus::prelude::*;
fn main() {
dioxus::desktop::start(App, |c| c);
dioxus::desktop::launch(App, |c| c);
}
fn App((cx, props): Component<()>) -> Element {
fn App(cx: Context, props: &()) -> Element {
cx.render(rsx! (
div { "Hello, world!" }
))
@@ -108,7 +108,7 @@ At this point, you could call `cargo run` and be greeted with a simple `Hello, W
### Dissecting our example
This bit of code imports everything from the the `prelude` module. This brings into scope the right traits, types, and macros needed for working with Dioxus.
The `use` statement at the top of our app imports everything from the the `prelude` module. `use`-ing the prelude imports the right traits, types, and macros needed for working with Dioxus.
```rust
use dioxus::prelude::*;
@@ -118,44 +118,34 @@ This initialization code launches a Tokio runtime on a helper thread where your
```rust
fn main() {
dioxus::desktop::start(App, |c| c);
dioxus::desktop::launch(App, |c| c);
}
```
Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Element`.
```rust
fn App((cx, props): Component<()>) -> Element {
fn App(cx: Context, props: &()) -> Element {
cx.render(rsx! {
div { "Hello, world!" }
})
}
```
In cases where props need to borrow from their parent, you will need to specify lifetimes using the function syntax:
Writing `fn App(cx: Context, props: &()) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data.
```rust
fn App<'a>(cx: Component<'a, ()>) -> Element<'a> {
cx.render(rsx! {
div { "Hello, world!" }
})
}
static App: FC<()> = |cx, props| cx.render(rsx!(div { "Hello, world!" }));
```
Writing `fn App((cx, props): Component<()>) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data.
```rust
static App: Fc<()> = |cx, props| {
cx.render(rsx! {
div { "Hello, world!" }
})
};
```
### What is this `Context` object?
### The `Context` object
Coming from React, the `Context` object might be confusing. In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering.
In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data.
In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data. The `Context` object provides a handful of useful APIs for features like suspense, rendering, and more.
### The `rsx!` macro
## Moving on
Congrats! You've built your first desktop application with Dioxus. Next, we're going to learn about the basics of building interactive user interfaces.
Next, we're greeted with the `rsx!` macro. This lets us add a custom DSL for declaratively building the structure of our app. The semantics of this macro are similar to that of JSX and HTML, though with a familiar Rust-y interface. The `html!` macro is also available for writing components with a JSX/HTML syntax.
The `rsx!` macro is lazy: it does not immediately produce elements or allocates, but rather builds a closure which can be rendered with `cx.render`.
+10
View File
@@ -20,6 +20,16 @@ Dioxus requires a few main things to get up and running:
Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more.
### Installing Rust
Head over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler.
Once installed, make sure to install wasm32-unknown-unknown as a target if you're planning on deploying your app to the web.
```
rustup target add wasm32-unknown-uknown
```
### Dioxus-CLI for dev server, bundling, etc.
We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. To install the CLI, you'll need cargo (should be automatically installed with Rust):