Merge branch 'master' into jk/remove-dioxus-bin
This commit is contained in:
259 files changed
+11706
-4788
No files matched your search
@@ -74,10 +74,9 @@ fn app(cx: Scope<usize>) -> Element {
|
||||
button {
|
||||
onclick: move |_| {
|
||||
to_owned![count];
|
||||
let sc = cx.sc();
|
||||
async move {
|
||||
// Call the server function just like a local async function
|
||||
if let Ok(new_count) = double_server(sc, *count.current()).await {
|
||||
if let Ok(new_count) = double_server(*count.current()).await {
|
||||
count.set(new_count);
|
||||
}
|
||||
}
|
||||
@@ -89,7 +88,8 @@ fn app(cx: Scope<usize>) -> Element {
|
||||
|
||||
// We use the "getcbor" encoding to make caching easier
|
||||
#[server(DoubleServer, "", "getcbor")]
|
||||
async fn double_server(cx: DioxusServerContext, number: usize) -> Result<usize, ServerFnError> {
|
||||
async fn double_server(number: usize) -> Result<usize, ServerFnError> {
|
||||
let cx = server_context();
|
||||
// Perform some expensive computation or access a database on the server
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
let result = number * 2;
|
||||
|
||||
@@ -92,10 +92,9 @@ fn app(cx: Scope<usize>) -> Element {
|
||||
button {
|
||||
onclick: move |_| {
|
||||
to_owned![count];
|
||||
let sc = cx.sc();
|
||||
async move {
|
||||
// Call the server function just like a local async function
|
||||
if let Ok(new_count) = double_server(sc, *count.current()).await {
|
||||
if let Ok(new_count) = double_server(*count.current()).await {
|
||||
count.set(new_count);
|
||||
}
|
||||
}
|
||||
@@ -107,10 +106,11 @@ fn app(cx: Scope<usize>) -> Element {
|
||||
|
||||
// We use the "getcbor" encoding to make caching easier
|
||||
#[server(DoubleServer, "", "getcbor")]
|
||||
async fn double_server(cx: DioxusServerContext, number: usize) -> Result<usize, ServerFnError> {
|
||||
async fn double_server(number: usize) -> Result<usize, ServerFnError> {
|
||||
// Perform some expensive computation or access a database on the server
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
let result = number * 2;
|
||||
let cx = server_context();
|
||||
|
||||
println!(
|
||||
"User Agent {:?}",
|
||||
|
||||
@@ -21,10 +21,12 @@
|
||||
- [Hooks & Component State](interactivity/hooks.md)
|
||||
- [User Input](interactivity/user_input.md)
|
||||
- [Sharing State](interactivity/sharing_state.md)
|
||||
- [Memoization](interactivity/memoization.md)
|
||||
- [Custom Hooks](interactivity/custom_hooks.md)
|
||||
- [Dynamic Rendering](interactivity/dynamic_rendering.md)
|
||||
- [Routing](interactivity/router.md)
|
||||
- [Async](async/index.md)
|
||||
- [UseEffect](async/use_effect.md)
|
||||
- [UseFuture](async/use_future.md)
|
||||
- [UseCoroutine](async/use_coroutine.md)
|
||||
- [Spawning Futures](async/spawn.md)
|
||||
|
||||
@@ -94,8 +94,10 @@ Calling `deref` or `deref_mut` is actually more complex than it seems. When a va
|
||||
|
||||
Sometimes you want a signal to propagate across your app, either through far-away siblings or through deeply-nested components. In these cases, we use Dirac: Dioxus's first-class state management toolkit. Dirac atoms automatically implement the Signal API. This component will bind the input element to the `TITLE` atom.
|
||||
|
||||
|
||||
```rust, no_run
|
||||
const TITLE: Atom<String> = || "".to_string();
|
||||
const TITLE: Atom<String> = Atom(|| "".to_string());
|
||||
|
||||
const Provider: Component = |cx|{
|
||||
let title = use_signal(cx, &TITLE);
|
||||
render!(input { value: title })
|
||||
@@ -131,7 +133,8 @@ By default, Dioxus is limited when you use iter/map. With the `For` component, y
|
||||
Dioxus automatically understands how to use your signals when mixed with iterators through `Deref`/`DerefMut`. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster.
|
||||
|
||||
```rust, no_run
|
||||
const DICT: AtomFamily<String, String> = |_| {};
|
||||
const DICT: AtomFamily<String, String> = AtomFamily(|_| {});
|
||||
|
||||
const List: Component = |cx|{
|
||||
let dict = use_signal(cx, &DICT);
|
||||
cx.render(rsx!(
|
||||
@@ -142,14 +145,6 @@ const List: Component = |cx|{
|
||||
};
|
||||
```
|
||||
|
||||
## Remote Signals
|
||||
|
||||
Apps that use signals will enjoy a pleasant hybrid of server-side and client-side rendering.
|
||||
|
||||
```rust, no_run
|
||||
|
||||
```
|
||||
|
||||
## How does it work?
|
||||
|
||||
Signals internally use Dioxus' asynchronous rendering infrastructure to perform updates out of the tree.
|
||||
@@ -143,7 +143,7 @@ async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
|
||||
We can combine coroutines with [Fermi](https://docs.rs/fermi/latest/fermi/index.html) to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state _within_ a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your _actual_ state does not need to be tied up in a system like Fermi or Redux – the only Atoms that need to exist are those that are used to drive the display/UI.
|
||||
|
||||
```rust, no_run
|
||||
static USERNAME: Atom<String> = |_| "default".to_string();
|
||||
static USERNAME: Atom<String> = Atom(|_| "default".to_string());
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let atoms = use_atom_root(cx);
|
||||
@@ -156,7 +156,7 @@ fn app(cx: Scope) -> Element {
|
||||
}
|
||||
|
||||
fn Banner(cx: Scope) -> Element {
|
||||
let username = use_read(cx, USERNAME);
|
||||
let username = use_read(cx, &USERNAME);
|
||||
|
||||
cx.render(rsx!{
|
||||
h1 { "Welcome back, {username}" }
|
||||
@@ -174,8 +174,8 @@ enum SyncAction {
|
||||
}
|
||||
|
||||
async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
|
||||
let username = atoms.write(USERNAME);
|
||||
let errors = atoms.write(ERRORS);
|
||||
let username = atoms.write(&USERNAME);
|
||||
let errors = atoms.write(&ERRORS);
|
||||
|
||||
while let Ok(msg) = rx.next().await {
|
||||
match msg {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# UseEffect
|
||||
|
||||
[`use_effect`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_effect.html) lets you run a callback that returns a future, which will be re-run when its [dependencies](#dependencies) change. This is useful to syncrhonize with external events.
|
||||
|
||||
## Dependencies
|
||||
|
||||
You can make the callback re-run when some value changes. For example, you might want to fetch a user's data only when the user id changes. You can provide a tuple of "dependencies" to the hook. It will automatically re-run it when any of those dependencies change.
|
||||
|
||||
## Example
|
||||
|
||||
```rust, no_run
|
||||
#[inline_props]
|
||||
fn Profile(cx: Scope, id: usize) -> Element {
|
||||
let name = use_state(cx, || None);
|
||||
|
||||
// Only fetch the user data when the id changes.
|
||||
use_effect(cx, (id,), |(id,)| {
|
||||
to_owned![name];
|
||||
async move {
|
||||
let user = fetch_user(id).await;
|
||||
name.set(user.name);
|
||||
}
|
||||
});
|
||||
|
||||
// Because the dependencies are empty, this will only run once.
|
||||
// An empty tuple is always equal to an empty tuple.
|
||||
use_effect(cx, (), |()| async move {
|
||||
println!("Hello, World!");
|
||||
});
|
||||
|
||||
let name = name.get().clone().unwrap_or("Loading...".to_string());
|
||||
|
||||
render!(
|
||||
p { "{name}" }
|
||||
)
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
render!(Profile { id: 0 })
|
||||
}
|
||||
```
|
||||
@@ -113,14 +113,14 @@ enum InputError {
|
||||
TooShort,
|
||||
}
|
||||
|
||||
static INPUT_ERROR: Atom<InputError> = |_| InputError::None;
|
||||
static INPUT_ERROR: Atom<InputError> = Atom(|_| InputError::None);
|
||||
```
|
||||
|
||||
Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree.
|
||||
|
||||
```rust, no_run
|
||||
fn TopLevel(cx: Scope) -> Element {
|
||||
let error = use_read(cx, INPUT_ERROR);
|
||||
let error = use_read(cx, &INPUT_ERROR);
|
||||
|
||||
match error {
|
||||
TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
|
||||
@@ -134,7 +134,7 @@ Now, whenever a downstream component has an error in its actions, it can simply
|
||||
|
||||
```rust, no_run
|
||||
fn Commandline(cx: Scope) -> Element {
|
||||
let set_error = use_set(cx, INPUT_ERROR);
|
||||
let set_error = use_set(cx, &INPUT_ERROR);
|
||||
|
||||
cx.render(rsx!{
|
||||
input {
|
||||
|
||||
@@ -14,13 +14,13 @@ We start will a hello world program. This program renders a desktop app with the
|
||||
|
||||
## The rsx! Macro
|
||||
|
||||
Before the Rust compiler runs the program, it will expand all macros. Here is what the hello world example looks like expanded:
|
||||
Before the Rust compiler runs the program, it will expand all [macros](https://doc.rust-lang.org/reference/procedural-macros.html). Here is what the hello world example looks like expanded:
|
||||
|
||||
```rust, no_run
|
||||
{{#include ../../../examples/readme_expanded.rs}}
|
||||
```
|
||||
|
||||
The rsx macro separates the static parts of the rsx (the template) and the dynamic parts (the dynamic_nodes and dynamic_attributes).
|
||||
The rsx macro separates the static parts of the rsx (the template) and the dynamic parts (the [dynamic_nodes](https://docs.rs/dioxus-core/0.3.2/dioxus_core/prelude/struct.VNode.html#structfield.dynamic_nodes) and [dynamic_attributes](https://docs.rs/dioxus-core/0.3.2/dioxus_core/prelude/struct.VNode.html#structfield.dynamic_attrs)).
|
||||
|
||||
The static template only contains the parts of the rsx that cannot change at runtime with holes for the dynamic parts:
|
||||
|
||||
@@ -32,17 +32,17 @@ The dynamic_nodes and dynamic_attributes are the parts of the rsx that can chang
|
||||
|
||||
## Launching the App
|
||||
|
||||
The app is launched by calling the `launch` function with the root component. Internally, this function will create a new web view using [wry](https://docs.rs/wry/latest/wry/) and create a virtual dom with the root component. This guide will not explain the renderer in-depth, but you can read more about it in the [custom renderer](/guide/custom-renderer) section.
|
||||
The app is launched by calling the `launch` function with the root component. Internally, this function will create a new web view using [wry](https://docs.rs/wry/latest/wry/) and create a virtual dom with the root component (`fn app()` in the readme example). This guide will not explain the renderer in-depth, but you can read more about it in the [custom renderer](/guide/custom-renderer) section.
|
||||
|
||||
## The Virtual DOM
|
||||
|
||||
Before we dive into the initial render in the virtual dom, we need to discuss what the virtual dom is. The virtual dom is a representation of the dom that is used to diff the current dom from the new dom. This diff is then used to create a list of mutations that need to be applied to the dom.
|
||||
Before we dive into the initial render in the virtual DOM, we need to discuss what the virtual DOM is. The virtual DOM is a representation of the DOM that is used to diff the current DOM from the new DOM. This diff is then used to create a list of mutations that need to be applied to the DOM to bring it into sync with the virtual DOM.
|
||||
|
||||
The Virtual Dom roughly looks like this:
|
||||
The Virtual DOM roughly looks like this:
|
||||
|
||||
```rust, no_run
|
||||
pub struct VirtualDom {
|
||||
// All the templates that have been created or set durring hot reloading
|
||||
// All the templates that have been created or set during hot reloading
|
||||
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
|
||||
|
||||
// A slab of all the scopes that have been created
|
||||
@@ -63,64 +63,74 @@ pub struct VirtualDom {
|
||||
```
|
||||
|
||||
> What is a [slab](https://docs.rs/slab/latest/slab/)?
|
||||
>
|
||||
> A slab acts like a hashmap with integer keys if you don't care about the value of the keys. It is internally backed by a dense vector which makes it more efficient than a hashmap. When you insert a value into a slab, it returns an integer key that you can use to retrieve the value later.
|
||||
|
||||
> How does Dioxus use slabs?
|
||||
> Dioxus uses "synchronized slabs" to communicate between the renderer and the VDOM. When an node is created in the Virtual Dom, a ElementId is passed along with the mutation to the renderer to identify the node. These ids are used by the Virtual Dom to reference that nodes in future mutations like setting an attribute on a node or removing a node.
|
||||
> When the renderer sends an event to the Virtual Dom, it sends the ElementId of the node that the event was triggered on. The Virtual Dom uses this id to find the node in the slab and then run the necessary event handlers.
|
||||
>
|
||||
> Dioxus uses "synchronized slabs" to communicate between the renderer and the VDOM. When a node is created in the Virtual DOM, an (elementId, mutation) pair is passed to the renderer to identify that node, which the renderer will then render in actual DOM. These ids are also used by the Virtual Dom to reference that node in future mutations, like setting an attribute on a node or removing a node. When the renderer sends an event to the Virtual Dom, it sends the ElementId of the node that the event was triggered on. The Virtual DOM uses this id to find that node in the slab and then run the necessary event handlers.
|
||||
|
||||
The virtual dom is a tree of scopes. A new scope is created for every component when it is first rendered and recycled when the component is unmounted.
|
||||
The virtual DOM is a tree of scopes. A new `Scope` is created for every component when it is first rendered and recycled when the component is unmounted.
|
||||
|
||||
Scopes serve three main purposes:
|
||||
|
||||
1. They store the state of hooks used by the component
|
||||
2. They store the state for the context API
|
||||
3. They store the current and previous VNode that was rendered for diffing
|
||||
2. They store the state for the context API (for example: using
|
||||
[use_shared_state_provider](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_shared_state_provider.html)).
|
||||
3. They store the current and previous versions of the `VNode` that was rendered, so they can be
|
||||
diffed to generate the set of mutations needed to re-render it.
|
||||
|
||||
### The Initial Render
|
||||
|
||||
The root scope is created and rebuilt:
|
||||
|
||||
1. The root component is run
|
||||
2. The root component returns a VNode
|
||||
3. Mutations for the VNode are created and added to the mutation list (this may involve creating new child components)
|
||||
4. The VNode is stored in the root scope
|
||||
2. The root component returns a `VNode`
|
||||
3. Mutations for this `VNode` are created and added to the mutation list (this may involve creating new child components)
|
||||
4. The `VNode` is stored in the root's `Scope`.
|
||||
|
||||
After the root scope is built, the mutations are sent to the renderer to be applied to the dom.
|
||||
After the root's `Scope` is built, all generated mutations are sent to the renderer, which applies them to the DOM.
|
||||
|
||||
After the initial render, the root scope looks like this:
|
||||
After the initial render, the root `Scope` looks like this:
|
||||
|
||||
[](https://mermaid.live/edit#pako:eNqtVE1P4zAQ_SuzPrWikRpWXCLtBRDisItWsOxhCaqM7RKricdyJrQV8N93QtvQNCkfEnOynydv3nxkHoVCbUQipjnOVSYDwc_L1AFbWd3dB-kzuEQkuFLoDUwDFkCZAek9nGDh0RlHK__atA1GkUUHf45f0YbppAqB_aOzIAvz-t7-chN_Y-1bw1WSJKsglIu2w9tktWXxIIuHURT5XCqTYa5NmDguw2R8c5MKq2GcgF46WTB_jafi9rZL0yi5q4jQTSrf9altO4okCn1Ratwyz55Qxuku2ITlTMgs6HCQimsPmb3PvqVi-L5gjXP3QcnxWnL8JZLrwGvR31n0KV-Bx6-r-oVkT_-3G1S-NQLbk9i8rj7udP2cixed2QcDCitHJiQw7ub3EVlNecrPjudG2-6soFO5VbMECmR9T5OnlUY4-AFxfw9aTFst3McU9TK1Otm6NEn_DubBYlX2_dglLXOz48FgwJmJ5lZTlhz6xWgNaFnyDgpymcARHO0W2a9J_l5w2wYXvHuGPcqaQ-rESBQmFNJq3nCPNZoK3l4sUSR81DLMUpG6Z_aTFeHV0imRUKjMSFReSzKnVnKGhUimMi8ZNdoShl-rlfmyOUfCS_cPcePz_B_Wl4pc)
|
||||
|
||||
### Waiting for Events
|
||||
|
||||
The Virtual Dom will only ever rerender a scope if it is marked as dirty. Each hook is responsible for marking the scope as dirty if the state has changed. Hooks can mark a scope as dirty by sending a message to the Virtual Dom's channel.
|
||||
The Virtual DOM will only ever re-render a `Scope` if it is marked as dirty. Each hook is responsible for marking the `Scope` as dirty if the state has changed. Hooks can mark a scope as dirty by sending a message to the Virtual Dom's channel. You can see the [implementations](https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks) for the hooks dioxus includes by default on how this is done. Calling `needs_update()` on a hook will also cause it to mark its scope as dirty.
|
||||
|
||||
There are generally two ways a scope is marked as dirty:
|
||||
|
||||
1. The renderer triggers an event: This causes an event listener to be called if needed which may mark a component as dirty
|
||||
2. The renderer calls wait for work: This polls futures which may mark a component as dirty
|
||||
1. The renderer triggers an event: An event listener on this event may be called, which may mark a
|
||||
component as dirty, if processing the event resulted in any generated any mutations.
|
||||
2. The renderer calls
|
||||
[`wait_for_work`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.wait_for_work):
|
||||
This polls dioxus internal future queue. One of these futures may mark a component as dirty.
|
||||
|
||||
Once at least one scope is marked as dirty, the renderer can call `render_with_deadline` to diff the dirty scopes.
|
||||
Once at least one `Scope` is marked as dirty, the renderer can call [`render_with_deadline`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.render_with_deadline) to diff the dirty scopes.
|
||||
|
||||
### Diffing Scopes
|
||||
|
||||
If the user clicked the "up high" button, the root scope would be marked as dirty by the use_state hook. Once the desktop renderer calls `render_with_deadline`, the root scope would be diffed.
|
||||
When a user clicks the "up high" button, the root `Scope` will be marked as dirty by the `use_state` hook. The desktop renderer will then call `render_with_deadline`, which will diff the root `Scope`.
|
||||
|
||||
To start the diffing process, the component is run. After the root component is run it will look like this:
|
||||
To start the diffing process, the component function is run. After the root component is run it, the root `Scope` will look like this:
|
||||
|
||||
[](https://mermaid.live/edit#pako:eNrFVlFP2zAQ_iuen0BrpCaIl0i8AEJ72KQJtpcRFBnbJVYTn-U4tBXw33dpG5M2CetoBfdkny_ffb67fPIT5SAkjekkhxnPmHXk-3WiCVpZ3T9YZjJyDeDIDQcjycRCQVwmCTOGXEBhQEvtVvG1CWUldwo0-XX-6vVIF5W1GB9cWVbI1_PNL5v8jW3uPFbpmFOc2HK-GfA2WG1ZeJSFx0EQmJxxmUEupE01liEd394mVAkyjolYaFYgfu1P6N1dF8Yzua-cA51WphtTWzsLc872Zan9CnEGUkktuk6fFm_i5NxFRwn9bUimHrIvCT3-N2EBM70j5XBNOTwI5TrxmvQJkr7ELcHx67Jeggz0v92g8q0RaE-iP1193On6NyxecKUeJeFQaSdtTMLu_Xah5ctT_u94Nty2ZwU0zxWfxqQA5PecPq84kq9nfRw7SK0WDiEFZ4O37d34S_-08lFBVfb92KVb5HIrAp0WpjKYKeGyODLz0dohWIkaZNkiJqfkdLvIH6oRaTSoEmm0n06k0a5K0ZdpL61Io0Yt0nfpxc7UQ0_9cJrhyZ8syX-6brS706Mc489Vjja7fbWj3cxDqIdfJJqOaCFtwZTAV8hT7U0ovjBQRmiMS8HsNKGJfsE4Vjm4WWhOY2crOaKVEczJS8WwgAWNJywv0SuFcmB_rJ41y9fNiBqm_wA0MS9_AUuAiy0)
|
||||
|
||||
Next, the Virtual Dom will compare the new VNode with the previous VNode and only update the parts of the tree that have changed.
|
||||
|
||||
When a component is re-rendered, the Virtual Dom will compare the new VNode with the previous VNode and only update the parts of the tree that have changed.
|
||||
Next, the Virtual DOM will compare the new VNode with the previous VNode and only update the parts of the tree that have changed. Because of this approach, when a component is re-rendered only the parts of the tree that have changed will be updated in the DOM by the renderer.
|
||||
|
||||
The diffing algorithm goes through the list of dynamic attributes and nodes and compares them to the previous VNode. If the attribute or node has changed, a mutation that describes the change is added to the mutation list.
|
||||
|
||||
Here is what the diffing algorithm looks like for the root scope (red lines indicate that a mutation was generated, and green lines indicate that no mutation was generated)
|
||||
Here is what the diffing algorithm looks like for the root `Scope` (red lines indicate that a mutation was generated, and green lines indicate that no mutation was generated)
|
||||
|
||||
[](https://mermaid.live/edit#pako:eNrFlFFPwjAQx7_KpT7Kko2Elya8qCE-aGLAJ5khpe1Yw9Zbug4k4He3OJjbGPig0T5t17tf_nf777aEo5CEkijBNY-ZsfAwDjW4kxfzhWFZDGNECxOOmYTIYAo2lsCyDG4xzVBLbcv8_RHKSG4V6orSIN0Wxrh8b2RYKr_uTyubd1W92GiWKg7aac6bOU3G803HbVk82xfP_Ok0JEqAT-FeLWJvpFYSOBbaSkMhCMnra5MgtfhWFrPWqHlhL2urT6atbU-oa0PNE8WXFFJ0-nazXakRroddGk9IwYEUnCd5w7Pddr5UTT8ZuVJY5F0fM7ebRLYyXNDgUnprJWxM-9lb7xAQLHe-M2xDYQCD9pD_2hez_kVn-P_rjLq6n3qjYv2iO5qz9DyvPdyv1ETp5eTTJ_7BGvQq8v1TVtl5jXUcRRcrqFh-dI4VtFlBN6t_ynLNkh5JpUmZEm5rbvfhkLiN6H4BQt2jYGYZklC_uzxWWJxsNCfUmkL2SJEJZuWdYs4cKaERS3IXlUJZNI_lGv7cxj2SMf2CeMx5_wBcbK19)
|
||||
|
||||
## Conclusion
|
||||
|
||||
This is only a brief overview of how the Virtual Dom works. There are several aspects not yet covered in this guide including how the Virtual Dom handles async-components, keyed diffing, and how it uses [bump allocation](https://github.com/fitzgen/bumpalo) to efficiently allocate VNodes. If need more information about the Virtual Dom, you can read the code of the [core](https://github.com/DioxusLabs/dioxus/tree/master/packages/core) crate or reach out to us on [Discord](https://discord.gg/XgGxMSkvUM).
|
||||
This is only a brief overview of how the Virtual Dom works. There are several aspects not yet covered in this guide including:
|
||||
|
||||
* How the Virtual DOM handles async-components
|
||||
* Keyed diffing
|
||||
* Using [bump allocation](https://github.com/fitzgen/bumpalo) to efficiently allocate VNodes.
|
||||
|
||||
If you need more information about the Virtual Dom, you can read the code of the [core](https://github.com/DioxusLabs/dioxus/tree/master/packages/core) crate or reach out to us on [Discord](https://discord.gg/XgGxMSkvUM).
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
So far our components have had no state like a normal rust functions. However, in a UI component, it is often useful to have stateful functionality to build user interactions. For example, you might want to track whether the user has opened a drop-down, and render different things accordingly.
|
||||
|
||||
Hooks allow us to create state in our components. Hooks are Rust functions that take a reference to `ScopeState` (in a component, you can pass `cx`), and provide you with functionality and state.
|
||||
Hooks allow us to create state in our components. Hooks are Rust functions that take a reference to [`ScopeState`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html) (in a component, you can pass `cx`), and provide you with functionality and state.
|
||||
|
||||
## `use_state` Hook
|
||||
|
||||
[`use_state`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_state.html) is one of the simplest hooks.
|
||||
|
||||
- You provide a closure that determines the initial value
|
||||
- You provide a closure that determines the initial value: `let mut count = use_state(cx, || 0);`
|
||||
- `use_state` gives you the current value, and a way to update it by setting it to something else
|
||||
- When the value updates, `use_state` makes the component re-render, and provides you with the new value
|
||||
- When the value updates, `use_state` makes the component re-render (along with any other component
|
||||
that references it), and then provides you with the new value.
|
||||
|
||||
For example, you might have seen the counter example, in which state (a number) is tracked using the `use_state` hook:
|
||||
|
||||
@@ -45,10 +46,11 @@ But how can Dioxus differentiate between multiple hooks in the same component? A
|
||||
This is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. Because the order you call hooks matters, you must follow certain rules when using hooks:
|
||||
|
||||
1. Hooks may be only used in components or other hooks (we'll get to that later)
|
||||
2. On every call to the component function
|
||||
2. On every call to a component function
|
||||
1. The same hooks must be called (except in the case of early returns, as explained later in the [Error Handling chapter](../best_practices/error_handling.md))
|
||||
2. In the same order
|
||||
3. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions
|
||||
3. Hook names should start with `use_` so you don't accidentally confuse them with regular
|
||||
functions (`use_state()`, `use_ref()`, `use_future()`, etc...)
|
||||
|
||||
These rules mean that there are certain things you can't do with hooks:
|
||||
|
||||
@@ -74,9 +76,12 @@ These rules mean that there are certain things you can't do with hooks:
|
||||
|
||||
`use_state` is great for tracking simple values. However, you may notice in the [`UseState` API](https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html) that the only way to modify its value is to replace it with something else (e.g., by calling `set`, or through one of the `+=`, `-=` operators). This works well when it is cheap to construct a value (such as any primitive). But what if you want to maintain more complex data in the components state?
|
||||
|
||||
For example, suppose we want to maintain a `Vec` of values. If we stored it with `use_state`, the only way to add a new value to the list would be to create a new `Vec` with the additional value, and put it in the state. This is expensive! We want to modify the existing `Vec` instead.
|
||||
For example, suppose we want to maintain a `Vec` of values. If we stored it with `use_state`, the
|
||||
only way to add a new value to the list would be to copy the existing `Vec`, add our value to it,
|
||||
and then replace the existing `Vec` in the state with it. This is expensive! We want to modify the
|
||||
existing `Vec` instead.
|
||||
|
||||
Thankfully, there is another hook for that, `use_ref`! It is similar to `use_state`, but it lets you get a mutable reference to the contained data.
|
||||
Thankfully, there is another hook for that, `use_ref`! It **is** similar to `use_state`, but it lets you get a mutable reference to the contained data.
|
||||
|
||||
Here's a simple example that keeps a list of events in a `use_ref`. We can acquire write access to the state with `.with_mut()`, and then just `.push` a new value to the state:
|
||||
|
||||
@@ -84,4 +89,18 @@ Here's a simple example that keeps a list of events in a `use_ref`. We can acqui
|
||||
{{#include ../../../examples/hooks_use_ref.rs:component}}
|
||||
```
|
||||
|
||||
> The return values of `use_state` and `use_ref` (`UseState` and `UseRef`, respectively) are in some ways similar to [`Cell`](https://doc.rust-lang.org/std/cell/) and [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) – they provide interior mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered whenever you change the state.
|
||||
> The return values of `use_state` and `use_ref` (
|
||||
> [`UseState`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html) and
|
||||
> [`UseRef`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseRef.html), respectively) are in
|
||||
> some ways similar to [`Cell`](https://doc.rust-lang.org/std/cell/) and
|
||||
> [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) – they provide interior
|
||||
> mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered
|
||||
> whenever you change the state.
|
||||
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [**dioxus_hooks** ](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/) rustdoc
|
||||
- Documents all hook types included with dioxus by default Most of these are also covered in
|
||||
later chapters of this guide.
|
||||
- [Hooks Package](https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks)
|
||||
@@ -0,0 +1,19 @@
|
||||
# Memoization
|
||||
|
||||
[`use_memo`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_memo.html) let's you memorize values and thus save computation time. This is useful for expensive calculations.
|
||||
|
||||
```rust, no_run
|
||||
#[inline_props]
|
||||
fn Calculator(cx: Scope, number: usize) -> Element {
|
||||
let bigger_number = use_memo(cx, (number,), |(number,)| {
|
||||
// This will only be calculated when `number` has changed.
|
||||
number * 100
|
||||
});
|
||||
render!(
|
||||
p { "{bigger_number}" }
|
||||
)
|
||||
}
|
||||
fn app(cx: Scope) -> Element {
|
||||
render!(Calculator { number: 0 })
|
||||
}
|
||||
```
|
||||
@@ -32,7 +32,7 @@ Finally, a third component will render the other two as children. It will be res
|
||||
|
||||

|
||||
|
||||
## Using Context
|
||||
## Using Shared State
|
||||
|
||||
Sometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient.
|
||||
|
||||
@@ -42,7 +42,7 @@ Suppose now that we want to implement a dark mode toggle for our app. To achieve
|
||||
|
||||
Now, we could write another `use_state` in the top component, and pass `is_dark_mode` down to every component through props. But think about what will happen as the app grows in complexity – almost every component that renders any CSS is going to need to know if dark mode is enabled or not – so they'll all need the same dark mode prop. And every parent component will need to pass it down to them. Imagine how messy and verbose that would get, especially if we had components several levels deep!
|
||||
|
||||
Dioxus offers a better solution than this "prop drilling" – providing context. The [`use_context_provider`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) hook is similar to `use_ref`, but it makes it available through [`use_context`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) for all children components.
|
||||
Dioxus offers a better solution than this "prop drilling" – providing context. The [`use_shared_state_provider`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_shared_state_provider.html) hook is similar to `use_ref`, but it makes it available through [`use_shared_state`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_shared_state.html) for all children components.
|
||||
|
||||
First, we have to create a struct for our dark mode configuration:
|
||||
|
||||
@@ -62,7 +62,7 @@ As a result, any child component of `App` (direct or not), can access the `DarkM
|
||||
{{#include ../../../examples/meme_editor_dark_mode.rs:use_context}}
|
||||
```
|
||||
|
||||
> `use_context` returns `Option<UseSharedState<DarkMode>>` here. If the context has been provided, the value is `Some(UseSharedState<DarkMode>)`, which you can call `.read` or `.write` on, similarly to `UseRef`. Otherwise, the value is `None`.
|
||||
> `use_shared_state` returns `Option<UseSharedState<DarkMode>>` here. If the context has been provided, the value is `Some(UseSharedState<DarkMode>)`, which you can call `.read` or `.write` on, similarly to `UseRef`. Otherwise, the value is `None`.
|
||||
|
||||
For example, here's how we would implement the dark mode toggle, which both reads the context (to determine what color it should render) and writes to it (to toggle dark mode):
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
|
||||
Podemos combinar corrotinas com `Fermi` para emular o sistema `Thunk` do **Redux Toolkit** com muito menos dor de cabeça. Isso nos permite armazenar todo o estado do nosso aplicativo _dentro_ de uma tarefa e, em seguida, simplesmente atualizar os valores de "visualização" armazenados em `Atoms`. Não pode ser subestimado o quão poderosa é essa técnica: temos todas as vantagens das tarefas nativas do Rust com as otimizações e ergonomia do estado global. Isso significa que seu estado _real_ não precisa estar vinculado a um sistema como `Fermi` ou `Redux` – os únicos `Atoms` que precisam existir são aqueles que são usados para controlar a interface.
|
||||
|
||||
```rust, no_run
|
||||
static USERNAME: Atom<String> = |_| "default".to_string();
|
||||
static USERNAME: Atom<String> = Atom(|_| "default".to_string());
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let atoms = use_atom_root(cx);
|
||||
@@ -118,7 +118,7 @@ fn app(cx: Scope) -> Element {
|
||||
}
|
||||
|
||||
fn Banner(cx: Scope) -> Element {
|
||||
let username = use_read(cx, USERNAME);
|
||||
let username = use_read(cx, &USERNAME);
|
||||
|
||||
cx.render(rsx!{
|
||||
h1 { "Welcome back, {username}" }
|
||||
@@ -134,8 +134,8 @@ enum SyncAction {
|
||||
}
|
||||
|
||||
async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
|
||||
let username = atoms.write(USERNAME);
|
||||
let errors = atoms.write(ERRORS);
|
||||
let username = atoms.write(&USERNAME);
|
||||
let errors = atoms.write(&ERRORS);
|
||||
|
||||
while let Ok(msg) = rx.next().await {
|
||||
match msg {
|
||||
|
||||
@@ -113,14 +113,14 @@ enum InputError {
|
||||
TooShort,
|
||||
}
|
||||
|
||||
static INPUT_ERROR: Atom<InputError> = |_| InputError::None;
|
||||
static INPUT_ERROR: Atom<InputError> = Atom(|_| InputError::None);
|
||||
```
|
||||
|
||||
Então, em nosso componente de nível superior, queremos tratar explicitamente o possível estado de erro para esta parte da árvore.
|
||||
|
||||
```rust, no_run
|
||||
fn TopLevel(cx: Scope) -> Element {
|
||||
let error = use_read(cx, INPUT_ERROR);
|
||||
let error = use_read(cx, &INPUT_ERROR);
|
||||
|
||||
match error {
|
||||
TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
|
||||
@@ -134,7 +134,7 @@ Agora, sempre que um componente _downstream_ tiver um erro em suas ações, ele
|
||||
|
||||
```rust, no_run
|
||||
fn Commandline(cx: Scope) -> Element {
|
||||
let set_error = use_set(cx, INPUT_ERROR);
|
||||
let set_error = use_set(cx, &INPUT_ERROR);
|
||||
|
||||
cx.render(rsx!{
|
||||
input {
|
||||
|
||||
Reference in new issue
Block a user