docs: fix the reference code

This commit is contained in:
Jonathan Kelley 2022-01-03 13:26:15 -05:00
parent c34043f1c8
commit 3de776c42a
8 changed files with 300 additions and 85 deletions

View file

@ -1 +1,231 @@
# RSX in Depth # RSX in Depth
The RSX macro makes it very easy to assemble complex UIs with a very natural Rust syntax:
```rust
rsx!(
div {
button {
onclick: move |e| todos.write().new_todo(),
"Add todo"
}
ul {
class: "todo-list",
todos.iter().map(|(key, todo)| rsx!(
li {
class: "beautiful-todo"
key: "f"
h3 { "{todo.title}" }
p { "{todo.contents}"}
}
))
}
}
)
```
In this section, we'll cover the `rsx!` macro in depth. If you prefer to learn through examples, the `code reference` guide has plenty of examples on how to use `rsx!` effectively.
### Element structure
Attributes must come before child elements
```rust
div {
hidden: "false",
"some text"
child {}
Component {} // uppercase
component() // lowercase is treated like a function call
(0..10).map(|f| rsx!{ "hi {f}" }) // arbitrary rust expressions
}
```
Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work:
- `name: value` sets a property on this element.
- `"text"` adds a new text element
- `tag {}` adds a new child element
- `CustomTag {}` adds a new child component
- `{expr}` pastes the `expr` tokens literally. They must be `IntoIterator<T> where T: IntoVnode` to work properly
Commas are entirely optional, but might be useful to delineate between elements and attributes.
The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it.
### Ignoring `cx.render` with `rsx!(cx, ...)`
Sometimes, writing `cx.render` is a hassle. The `rsx! macro will accept any token followed by a comma as the target to call "render" on:
```rust
cx.render(rsx!( div {} ))
// becomes
rsx!(cx, div {})
```
### Conditional Rendering
Sometimes, you might not want to render an element given a condition. The rsx! macro will accept any tokens directly contained with curly braces, provided they resolve to a type that implements `IntoIterator<VNode>`. This lets us write any Rust expression that resolves to a VNode:
```rust
rsx!({
if enabled {
rsx!(cx, div {"enabled"})
} else {
rsx!(cx, li {"disabled"})
}
})
```
A convenient way of hiding/showing an element is returning an `Option<VNode>`. When combined with `and_then`, we can succinctly control the display state given some boolean:
```rust
rsx!({
a.and_then(rsx!(div {"enabled"}))
})
```
It's important to note that the expression `rsx!()` is typically lazy - this expression must be _rendered_ to produce a VNode. When using match statements, we must render every arm as to avoid the `no two closures are identical` rule that Rust imposes:
```rust
// this will not compile!
match case {
true => rsx!(div {}),
false => rsx!(div {})
}
// the nodes must be rendered first
match case {
true => rsx!(cx, div {}),
false => rsx!(cx, div {})
}
```
### Lists
Again, because anything that implements `IntoIterator<VNode>` is valid, we can use lists directly in our `rsx!`:
```rust
let items = vec!["a", "b", "c"];
cx.render(rsx!{
ul {
{items.iter().map(|f| rsx!(li { "a" }))}
}
})
```
Sometimes, it makes sense to render VNodes into a list:
```rust
let mut items = vec![];
for _ in 0..5 {
items.push(rsx!(cx, li {} ))
}
rsx!(cx, {items} )
```
#### Lists and Keys
When rendering the VirtualDom to the screen, Dioxus needs to know which elements have been added and which have been removed. These changes are determined through a process called "diffing" - an old set of elements is compared to a new set of elements. If an element is removed, then it won't show up in the new elements, and Dioxus knows to remove it.
However, with lists, Dioxus does not exactly know how to determine which elements have been added or removed if the order changes or if an element is added or removed from the middle of the list.
In these cases, it is vitally important to specify a "key" alongside the element. Keys should be persistent between renders.
```rust
fn render_list(cx: Scope, items: HashMap<String, Todo>) -> DomTree {
rsx!(cx, ul {
{items.iter().map(|key, item| {
li {
key: key,
h2 { "{todo.title}" }
p { "{todo.contents}" }
}
})}
})
}
```
There have been many guides made for keys in React, so we recommend reading up to understand their importance:
- [React guide on keys](https://reactjs.org/docs/lists-and-keys.html)
- [Importance of keys (Medium)](https://kentcdodds.com/blog/understanding-reacts-key-prop)
### Complete Reference
```rust
let text = "example";
cx.render(rsx!{
div {
h1 { "Example" },
{title}
// fstring interpolation
"{text}"
p {
// Attributes
tag: "type",
// Anything that implements display can be an attribute
abc: 123,
enabled: true,
// attributes also supports interpolation
// `class` is not a restricted keyword unlike JS and ClassName
class: "big small wide short {text}",
class: format_args!("attributes take fmt::Arguments. {}", 99),
tag: {"these tokens are placed directly"}
// Children
a { "abcder" },
// Children with attributes
h2 { "hello", class: "abc-123" },
// Child components
CustomComponent { a: 123, b: 456, key: "1" },
// Child components with paths
crate::components::CustomComponent { a: 123, b: 456, key: "1" },
// Iterators
{ (0..3).map(|i| rsx!( h1 {"{:i}"} )) },
// More rsx!, or even html!
{ rsx! { div { } } },
{ html! { <div> </div> } },
// Matching
// Requires rendering the nodes first.
// rsx! is lazy, and the underlying closures cannot have the same type
// Rendering produces the VNode type
{match rand::gen_range::<i32>(1..3) {
1 => rsx!(cx, h1 { "big" })
2 => rsx!(cx, h2 { "medium" })
_ => rsx!(cx, h3 { "small" })
}}
// Optionals
{true.and_then(|f| rsx!( h1 {"Conditional Rendering"} ))}
// Child nodes
{cx.props.children}
// Any expression that is `IntoVNode`
{expr}
}
}
})
```

View file

@ -1,4 +1,4 @@
# Getting Stated: Server-Side-Rendering # Getting Started: Server-Side-Rendering
The Dioxus VirtualDom can be rendered to a string by traversing the Element Tree. This is implemented in the `ssr` crate where your Dioxus app can be directly rendered to HTML to be served by a web server. The Dioxus VirtualDom can be rendered to a string by traversing the Element Tree. This is implemented in the `ssr` crate where your Dioxus app can be directly rendered to HTML to be served by a web server.

View file

@ -3,27 +3,19 @@
This folder holds a bunch of useful reference code. However, none of the code is meant to be executed (nothing special will happen). This folder holds a bunch of useful reference code. However, none of the code is meant to be executed (nothing special will happen).
| Reference: | What it does | | Reference: | What it does |
| --------------------------------------------------- | ----------------------------------- | | --------------------------------------------------- | ------------------------------------------------------ |
| [Basics](./basics.rs) | How to handle futures with suspense | | [Basics](./basics.rs) | How to render a simple app |
| [Empty](./empty.rs) | How to handle futures with suspense | | [Empty](./empty.rs) | Components can return None |
| [Children](./children.rs) | How to handle futures with suspense | | [Children](./children.rs) | Components can have children |
| [Async](./async.rs) | How to handle futures with suspense | | [Suspense](./suspense.rs) | Suspense can be achieved with use_suspense |
| [Suspense](./suspense.rs) | How to handle futures with suspense | | [Conditional Rendering](./conditional_rendering.rs) | Elements can be hidden conditionally |
| [Conditional Rendering](./conditional_rendering.rs) | How to handle futures with suspense | | [Controlled Inputs](./controlled_inputs.rs) | Inputs are "controlled" similar to React |
| [Controlled Inputs](./controlled_inputs.rs) | How to handle futures with suspense | | [Fragments](./fragments.rs) | Components can return multiple elements without a root |
| [Fragments](./fragments.rs) | How to handle futures with suspense | | [Inline Styles](./inline_styles.rs) | Styles can be inlined as element attributes |
| [Inline Styles](./inline_styles.rs) | How to handle futures with suspense | | [Spread Pattern for Props](./spreadpattern.rs) | Props can be spread into component calls |
| [Spread Pattern for Props](./spreadpattern.rs) | How to handle futures with suspense | | [Iterators](./iterators.rs) | Lists of elements can be made from iterators |
| [Iterators](./iterators.rs) | How to handle futures with suspense | | [Listener](./listener.rs) | State can be updated from event listeners |
| [Listener](./listener.rs) | How to handle futures with suspense | | [Memo](./memo.rs) | Memoization is controlled via PartialEq |
| [Memo](./memo.rs) | How to handle futures with suspense | | [Custom Elements](./custom_elements.rs) | Define custom elements |
| [NodeRef](./noderefs.rs) | How to handle futures with suspense | | [Anti-patterns](./antipatterns.rs) | What not to do! |
| [Signals](./signals.rs) | How to handle futures with suspense |
| [ToString](./tostring.rs) | How to handle futures with suspense |
| [Global CSS](./global_css.rs) | How to handle futures with suspense |
| [State Management](./statemanagement.rs) | How to handle futures with suspense |
| [Testing](./testing.rs) | How to handle futures with suspense |
| [Custom Elements](./custom_elements.rs) | How to handle futures with suspense |
| [Web Components](./webcomponents.rs) | How to handle futures with suspense |
| [Anti-patterns](./antipatterns.rs) | What not to do! |

View file

@ -13,26 +13,17 @@ use dioxus::prelude::*;
pub fn Example(cx: Scope) -> Element { pub fn Example(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! {
head { head {
style: { background_color: "powderblue" } background_color: "powderblue"
} }
body { body {
h1 { style: { color: "blue" } h1 {
color: "blue",
"This is a heading" "This is a heading"
} }
p { style: { color: "red" } p {
color: "red",
"This is a paragraph" "This is a paragraph"
} }
} }
}) })
} }
// .... technically the rsx! macro is slightly broken at the moment and allows styles not wrapped in style {}
// I haven't noticed any name collisions yet, and am tentatively leaving this behavior in..
// Don't rely on it.
pub fn Example2(cx: Scope) -> Element {
cx.render(rsx! {
div { color: "red"
"hello world!"
}
})
}

View file

@ -28,7 +28,7 @@ pub struct MyProps<'a> {
children: Element<'a>, children: Element<'a>,
} }
pub fn Example1(cx: Scope<MyProps>) -> Element { pub fn Example1<'a>(cx: Scope<'a, MyProps<'a>>) -> Element {
let MyProps { count, live, name } = cx.props; let MyProps { count, live, name } = cx.props;
cx.render(rsx! { cx.render(rsx! {
div { div {

View file

@ -53,18 +53,20 @@ impl WebsysDom {
let listeners = FxHashMap::default(); let listeners = FxHashMap::default();
// re-hydrate the page - only supports one virtualdom per page // re-hydrate the page - only supports one virtualdom per page
// hydration is the dubmest thing you've ever heard of
// just blast away the page and replace it completely.
if cfg.hydrate { if cfg.hydrate {
// Load all the elements into the arena // // Load all the elements into the arena
let node_list: NodeList = document.query_selector_all("dioxus-id").unwrap(); // let node_list: NodeList = document.query_selector_all("dioxus-id").unwrap();
let len = node_list.length() as usize; // let len = node_list.length() as usize;
for x in 0..len { // for x in 0..len {
let node: Node = node_list.get(x as u32).unwrap(); // let node: Node = node_list.get(x as u32).unwrap();
let el: &Element = node.dyn_ref::<Element>().unwrap(); // let el: &Element = node.dyn_ref::<Element>().unwrap();
let id: String = el.get_attribute("dioxus-id").unwrap(); // let id: String = el.get_attribute("dioxus-id").unwrap();
let id = id.parse::<usize>().unwrap(); // let id = id.parse::<usize>().unwrap();
nodes[id] = Some(node); // nodes[id] = Some(node);
} // }
// Load all the event listeners into our listener register // Load all the event listeners into our listener register
// TODO // TODO

View file

@ -16,41 +16,41 @@
//! //!
//! To purview the examples, check of the root Dioxus crate - the examples in this crate are mostly meant to provide //! To purview the examples, check of the root Dioxus crate - the examples in this crate are mostly meant to provide
//! validation of websys-specific features and not the general use of Dioxus. //! validation of websys-specific features and not the general use of Dioxus.
//!
//! ## RequestAnimationFrame and RequestIdleCallback // ## RequestAnimationFrame and RequestIdleCallback
//! ------------------------------------------------ // ------------------------------------------------
//! React implements "jank free rendering" by deliberately not blocking the browser's main thread. For large diffs, long // React implements "jank free rendering" by deliberately not blocking the browser's main thread. For large diffs, long
//! running work, and integration with things like React-Three-Fiber, it's extremeley important to avoid blocking the // running work, and integration with things like React-Three-Fiber, it's extremeley important to avoid blocking the
//! main thread. // main thread.
//! //
//! React solves this problem by breaking up the rendering process into a "diff" phase and a "render" phase. In Dioxus, // React solves this problem by breaking up the rendering process into a "diff" phase and a "render" phase. In Dioxus,
//! the diff phase is non-blocking, using "yield_now" to allow the browser to process other events. When the diff phase // the diff phase is non-blocking, using "yield_now" to allow the browser to process other events. When the diff phase
//! is finally complete, the VirtualDOM will return a set of "Mutations" for this crate to apply. // is finally complete, the VirtualDOM will return a set of "Mutations" for this crate to apply.
//! //
//! Here, we schedule the "diff" phase during the browser's idle period, achieved by calling RequestIdleCallback and then // Here, we schedule the "diff" phase during the browser's idle period, achieved by calling RequestIdleCallback and then
//! setting a timeout from the that completes when the idleperiod is over. Then, we call requestAnimationFrame // setting a timeout from the that completes when the idleperiod is over. Then, we call requestAnimationFrame
//! //
//! From Google's guide on rAF and rIC: // From Google's guide on rAF and rIC:
//! ----------------------------------- // -----------------------------------
//! //
//! If the callback is fired at the end of the frame, it will be scheduled to go after the current frame has been committed, // If the callback is fired at the end of the frame, it will be scheduled to go after the current frame has been committed,
//! which means that style changes will have been applied, and, importantly, layout calculated. If we make DOM changes inside // which means that style changes will have been applied, and, importantly, layout calculated. If we make DOM changes inside
//! of the idle callback, those layout calculations will be invalidated. If there are any kind of layout reads in the next // of the idle callback, those layout calculations will be invalidated. If there are any kind of layout reads in the next
//! frame, e.g. getBoundingClientRect, clientWidth, etc, the browser will have to perform a Forced Synchronous Layout, // frame, e.g. getBoundingClientRect, clientWidth, etc, the browser will have to perform a Forced Synchronous Layout,
//! which is a potential performance bottleneck. // which is a potential performance bottleneck.
//! //
//! Another reason not trigger DOM changes in the idle callback is that the time impact of changing the DOM is unpredictable, // Another reason not trigger DOM changes in the idle callback is that the time impact of changing the DOM is unpredictable,
//! and as such we could easily go past the deadline the browser provided. // and as such we could easily go past the deadline the browser provided.
//! //
//! The best practice is to only make DOM changes inside of a requestAnimationFrame callback, since it is scheduled by the // The best practice is to only make DOM changes inside of a requestAnimationFrame callback, since it is scheduled by the
//! browser with that type of work in mind. That means that our code will need to use a document fragment, which can then // browser with that type of work in mind. That means that our code will need to use a document fragment, which can then
//! be appended in the next requestAnimationFrame callback. If you are using a VDOM library, you would use requestIdleCallback // be appended in the next requestAnimationFrame callback. If you are using a VDOM library, you would use requestIdleCallback
//! to make changes, but you would apply the DOM patches in the next requestAnimationFrame callback, not the idle callback. // to make changes, but you would apply the DOM patches in the next requestAnimationFrame callback, not the idle callback.
//! //
//! Essentially: // Essentially:
//! ------------ // ------------
//! - Do the VDOM work during the idlecallback // - Do the VDOM work during the idlecallback
//! - Do DOM work in the next requestAnimationFrame callback // - Do DOM work in the next requestAnimationFrame callback
use std::rc::Rc; use std::rc::Rc;