mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 14:44:12 +00:00
chore: update docs
This commit is contained in:
parent
03aea885cf
commit
16a521a601
5 changed files with 75 additions and 62 deletions
|
@ -34,7 +34,6 @@ indexmap = "1.7"
|
|||
# Serialize the Edits for use in Webview/Liveview instances
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
anyhow = "1.0.66"
|
||||
bumpslab = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "*", features = ["full"] }
|
||||
|
@ -43,4 +42,3 @@ dioxus = { path = "../dioxus" }
|
|||
[features]
|
||||
default = []
|
||||
serialize = ["serde"]
|
||||
debug_vdom = []
|
||||
|
|
|
@ -1,52 +1,84 @@
|
|||
# Dioxus-core
|
||||
# dioxus-core
|
||||
|
||||
This is the core crate for the Dioxus Virtual DOM. This README will focus on the technical design and layout of this Virtual DOM implementation. If you want to read more about using Dioxus, then check out the Dioxus crate, documentation, and website.
|
||||
dioxus-core is a fast and featureful VirtualDom implementation written in and for Rust.
|
||||
|
||||
To build new apps with Dioxus or to extend the ecosystem with new hooks or components, use the higher-level `dioxus` crate with the appropriate feature flags.
|
||||
# Features
|
||||
|
||||
- Functions as components
|
||||
- Hooks for local state
|
||||
- Task pool for spawning futures
|
||||
- Template-based architecture
|
||||
- Asynchronous components
|
||||
- Suspense boundaries
|
||||
- Error boundaries through the `anyhow` crate
|
||||
- Customizable memoization
|
||||
|
||||
If just starting out, check out the Guides first.
|
||||
|
||||
# General Theory
|
||||
|
||||
The dioxus-core `VirtualDom` object is built around the concept of a `Template`. Templates describe a layout tree known at compile time with dynamic parts filled at runtime.
|
||||
|
||||
Each component in the VirtualDom works as a dedicated render loop where re-renders are triggered by events external to the VirtualDom, or from the components themselves.
|
||||
|
||||
When each component re-renders, it must return an `Element`. In Dioxus, the `Element` type is an alias for `Result<VNode>`. Between two renders, Dioxus compares the inner `VNode` object, and calculates the differences of the dynamic portions of each internal `Template`. If any attributes or elements are different between the old layout and new layout, Dioxus will write modifications to the `Mutations` object.
|
||||
|
||||
Dioxus expects the target renderer to save its nodes in a list. Each element is given a numerical ID which can be used to directly index into that list for O(1) lookups.
|
||||
|
||||
# Usage
|
||||
|
||||
All Dioxus apps start as just a function that takes the [`Scope`] object and returns an [`Element`].
|
||||
|
||||
The `dioxus` crate exports the `rsx` macro which transforms a helpful, simpler syntax of Rust into the logic required to build Templates.
|
||||
|
||||
First, start with your app:
|
||||
|
||||
```rust, ignore
|
||||
fn app(cx: Scope) -> Element {
|
||||
render!(div { "hello world" })
|
||||
cx.render(rsx!( div { "hello world" } ))
|
||||
}
|
||||
```
|
||||
|
||||
fn main() {
|
||||
let mut renderer = SomeRenderer::new();
|
||||
Then, we'll want to create a new VirtualDom using this app as the root component.
|
||||
|
||||
// Creating a new virtualdom from a component
|
||||
let mut dom = VirtualDom::new(app);
|
||||
```rust, ingore
|
||||
let mut dom = VirtualDom::new(app);
|
||||
```
|
||||
|
||||
// Patching the renderer with the changes to draw the screen
|
||||
let edits = dom.rebuild();
|
||||
renderer.apply(edits);
|
||||
To build the app into a stream of mutations, we'll use [`VirtualDom::rebuild`]:
|
||||
|
||||
// Injecting events
|
||||
dom.handle_message(SchedulerMsg::Event(UserEvent {
|
||||
scope_id: None,
|
||||
priority: EventPriority::High,
|
||||
element: ElementId(0),
|
||||
name: "onclick",
|
||||
data: Arc::new(()),
|
||||
}));
|
||||
```rust, ignore
|
||||
let mutations = dom.rebuild();
|
||||
|
||||
// polling asynchronously
|
||||
dom.wait_for_work().await;
|
||||
apply_edits_to_real_dom(mutations);
|
||||
```
|
||||
|
||||
// working with a deadline
|
||||
if let Some(edits) = dom.work_with_deadline(|| false) {
|
||||
renderer.apply(edits);
|
||||
We can then wait for any asynchronous components or pending futures using the `wait_for_work()` method. If we have a deadline, then we can use render_with_deadline instead:
|
||||
|
||||
```rust, ignore
|
||||
// Wait for the dom to be marked dirty internally
|
||||
dom.wait_for_work().await;
|
||||
|
||||
// Or wait for a deadline and then collect edits
|
||||
dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)));
|
||||
```
|
||||
|
||||
If an event occurs from outside the virtualdom while waiting for work, then we can cancel the wait using a `select!` block and inject the event.
|
||||
|
||||
```rust
|
||||
loop {
|
||||
tokio::select! {
|
||||
evt = real_dom.event() => dom.handle_event("click", evt.data, evt.element, evt.bubbles),
|
||||
_ = dom.wait_for_work() => {}
|
||||
}
|
||||
|
||||
// getting state of scopes
|
||||
let scope = dom.get_scope(ScopeId(0)).unwrap();
|
||||
// Render any work without blocking the main thread for too long
|
||||
let mutations = dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(10)));
|
||||
|
||||
// iterating through the tree
|
||||
match scope.root_node() {
|
||||
VNodes::Text(vtext) => dbg!(vtext),
|
||||
VNodes::Element(vel) => dbg!(vel),
|
||||
_ => todo!()
|
||||
}
|
||||
// And then apply the edits
|
||||
real_dom.apply(mutations);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Internals
|
||||
|
@ -82,17 +114,3 @@ The final implementation of Dioxus must:
|
|||
- Support server-side-rendering (SSR). VNodes should render to a string that can be served via a web server.
|
||||
- Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
|
||||
- Be modular. Components and hooks should be work anywhere without worrying about target platform.
|
||||
|
||||
|
||||
## Safety
|
||||
|
||||
Dioxus uses unsafe. The design of Dioxus *requires* unsafe (self-referential trees).
|
||||
|
||||
All of our test suite passes MIRI without errors.
|
||||
|
||||
Dioxus deals with arenas, lifetimes, asynchronous tasks, custom allocators, pinning, and a lot more foundational low-level work that is very difficult to implement with 0 unsafe.
|
||||
|
||||
If you don't want to use a crate that uses unsafe, then this crate is not for you.
|
||||
|
||||
However, we are always interested in decreasing the scope of the core VirtualDom to make it easier to review. We'd be happy to welcome PRs that can eliminate unsafe code while still upholding the numerous invariants required to execute certain features.
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use std::cell::Cell;
|
|||
use DynamicNode::*;
|
||||
|
||||
impl<'b> VirtualDom {
|
||||
pub fn diff_scope(&mut self, scope: ScopeId) {
|
||||
pub(super) fn diff_scope(&mut self, scope: ScopeId) {
|
||||
let scope_state = &mut self.scopes[scope.0];
|
||||
|
||||
self.scope_stack.push(scope);
|
||||
|
@ -59,7 +59,7 @@ impl<'b> VirtualDom {
|
|||
todo!("Not yet handling error rollover")
|
||||
}
|
||||
|
||||
pub fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
|
||||
fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
|
||||
if left_template.template.id != right_template.template.id {
|
||||
return self.light_diff_templates(left_template, right_template);
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ impl<'b> VirtualDom {
|
|||
/// This is mostly implemented to help solve the issue where the same component is rendered under two different
|
||||
/// conditions:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust, ignore
|
||||
/// if enabled {
|
||||
/// rsx!{ Component { enabled_sign: "abc" } }
|
||||
/// } else {
|
||||
|
@ -196,8 +196,7 @@ impl<'b> VirtualDom {
|
|||
/// However, we should not that it's explicit in the docs that this is not a guarantee. If you need to preserve state,
|
||||
/// then you should be passing in separate props instead.
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let props = if enabled {
|
||||
/// ComponentProps { enabled_sign: "abc" }
|
||||
/// } else {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
|
||||
mod any_props;
|
||||
mod arena;
|
||||
mod bump_frame;
|
||||
|
|
|
@ -94,9 +94,9 @@ use std::{
|
|||
/// ## Building an event loop around Dioxus:
|
||||
///
|
||||
/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
|
||||
/// ```rust
|
||||
/// ```rust, ignore
|
||||
/// fn app(cx: Scope) -> Element {
|
||||
/// cx.render(rsx!{
|
||||
/// cx.render(rsx! {
|
||||
/// div { "Hello World" }
|
||||
/// })
|
||||
/// }
|
||||
|
@ -121,7 +121,7 @@ use std::{
|
|||
/// where waiting on portions of the UI to finish rendering is important. To wait for suspense, use the
|
||||
/// [`VirtualDom::render_with_deadline`] method:
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust, ignore
|
||||
/// let dom = VirtualDom::new(app);
|
||||
///
|
||||
/// let deadline = tokio::time::sleep(Duration::from_millis(100));
|
||||
|
@ -134,7 +134,7 @@ use std::{
|
|||
/// suggest rendering with a deadline, and then looping between [`VirtualDom::wait_for_work`] and render_immediate until
|
||||
/// no suspended work is left.
|
||||
///
|
||||
/// ```
|
||||
/// ```rust, ignore
|
||||
/// let dom = VirtualDom::new(app);
|
||||
///
|
||||
/// let deadline = tokio::time::sleep(Duration::from_millis(20));
|
||||
|
@ -290,11 +290,6 @@ impl VirtualDom {
|
|||
self.dirty_scopes.insert(DirtyScope { height, id });
|
||||
}
|
||||
|
||||
/// Mark the entire tree as dirty.
|
||||
///
|
||||
/// Will force a re-render of every component
|
||||
pub fn mark_dirty_all(&mut self) {}
|
||||
|
||||
/// Determine whether or not a scope is currently in a suspended state
|
||||
///
|
||||
/// This does not mean the scope is waiting on its own futures, just that the tree that the scope exists in is
|
||||
|
@ -609,6 +604,7 @@ impl VirtualDom {
|
|||
|
||||
impl Drop for VirtualDom {
|
||||
fn drop(&mut self) {
|
||||
// Simply drop this scope which drops all of its children
|
||||
self.drop_scope(ScopeId(0));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue