mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
docs: fix the reference code
This commit is contained in:
parent
c34043f1c8
commit
3de776c42a
8 changed files with 300 additions and 85 deletions
|
@ -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}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -4,26 +4,18 @@ This folder holds a bunch of useful reference code. However, none of the code is
|
||||||
|
|
||||||
|
|
||||||
| 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 |
|
|
||||||
| [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! |
|
| [Anti-patterns](./antipatterns.rs) | What not to do! |
|
||||||
|
|
|
@ -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!"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue