chore: update docs

This commit is contained in:
Jonathan Kelley 2022-11-30 10:31:44 -05:00
parent 03aea885cf
commit 16a521a601
5 changed files with 75 additions and 62 deletions

View file

@ -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 = []

View file

@ -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.

View file

@ -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 {

View file

@ -1,3 +1,5 @@
#![doc = include_str!("../README.md")]
mod any_props;
mod arena;
mod bump_frame;

View file

@ -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));
}
}