wip: some docs and suspense
This commit is contained in:
1 parent
bab21a0aa1
commit
93d4b8ca7c
22 files changed
+210
-252
No files matched your search
+13
-10
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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?" ),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
))
|
||||
}
|
||||
```
|
||||
@@ -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}"
|
||||
}
|
||||
|
||||
@@ -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`.
|
||||
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user