mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
wip: docs
This commit is contained in:
parent
52c7154897
commit
8814977eee
14 changed files with 335 additions and 384 deletions
240
README.md
240
README.md
|
@ -49,7 +49,7 @@
|
||||||
Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
|
Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn App(cx: Context, props: &()) -> Element {
|
fn App(cx: Scope, props: &()) -> Element {
|
||||||
let mut count = use_state(cx, || 0);
|
let mut count = use_state(cx, || 0);
|
||||||
|
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
|
@ -65,12 +65,11 @@ Dioxus can be used to deliver webapps, desktop apps, static sites, liveview apps
|
||||||
If you know React, then you already know Dioxus.
|
If you know React, then you already know Dioxus.
|
||||||
|
|
||||||
### Unique features:
|
### Unique features:
|
||||||
- The most ergonomic and powerful state management of any Rust UI toolkit.
|
|
||||||
- Desktop apps running natively (no Electron!) in less than 10 lines of code.
|
- Desktop apps running natively (no Electron!) in less than 10 lines of code.
|
||||||
- Starting a new app takes zero templates or special tools - get a new app running in just seconds.
|
- Incredibly ergonomic and powerful state management.
|
||||||
- Incredible inline documentation. Supports hover and guides for all HTML elements, listeners, and events.
|
- Incredible inline documentation - hover and guides for all HTML elements, listeners, and events.
|
||||||
- Custom bump-allocator backing for all components. Nearly 0 allocations for steady-state components.
|
- Extremely memory efficient - 0 global allocations for steady-state components.
|
||||||
- Multithreaded asynchronous coroutine scheduler for powerful async code.
|
- Multithreaded asynchronous coroutine scheduler for first-class async support.
|
||||||
- And more! Read the full release post here.
|
- And more! Read the full release post here.
|
||||||
|
|
||||||
## Get Started with...
|
## Get Started with...
|
||||||
|
@ -95,24 +94,40 @@ If you know React, then you already know Dioxus.
|
||||||
|
|
||||||
See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.
|
See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.
|
||||||
|
|
||||||
## Why?
|
## Why Dioxus and why Rust?
|
||||||
|
|
||||||
TypeScript is a great addition to JavaScript, but comes with a lot of tweaking flags, a slight performance hit, and an uneven ecosystem where some of the most important packages are not properly typed. TypeScript provides a lot of great benefits to JS projects, but comes with its own "tax" that can slow down dev teams. Rust can be seen as a step up from TypeScript, supporting:
|
TypeScript is a fantastic addition to JavaScript, but it's still fundamentally JavaScript. TS code runs slightly slower, has tons of configuration options, and not every package is properly typed.
|
||||||
|
|
||||||
- static types for _all_ libraries
|
In contrast, Dioxus is written in Rust - which is almost like "TypeScript on steroids".
|
||||||
- advanced pattern matching
|
|
||||||
- immutability by default
|
|
||||||
- clean, composable iterators
|
|
||||||
- a good module system
|
|
||||||
- integrated documentation
|
|
||||||
- inline built-in unit/integration testing
|
|
||||||
- best-in-class error handling
|
|
||||||
- simple and fast build system (compared to WebPack!)
|
|
||||||
- powerful standard library (no need for lodash or underscore)
|
|
||||||
- include_str! for integrating html/css/svg templates directly
|
|
||||||
- various macros (`html!`, `rsx!`) for fast template iteration
|
|
||||||
|
|
||||||
And much more. Dioxus makes Rust apps just as fast to write as React apps, but affords more robustness, giving your frontend team greater confidence in making big changes in shorter time. Dioxus also works on the server, on the web, on mobile, on desktop - and it runs completely natively so performance is never an issue.
|
By using Rust, we gain:
|
||||||
|
|
||||||
|
- Static types for *every* library
|
||||||
|
- Immutability by default
|
||||||
|
- A simple and intuitive module system
|
||||||
|
- Integrated documentation (`go to source` _actually goes to source_)
|
||||||
|
- Advanced pattern matching
|
||||||
|
- Clean, efficient, composable iterators
|
||||||
|
- Inline built-in unit/integration testing
|
||||||
|
- Best-in-class error handling
|
||||||
|
- Powerful and sane, standard library
|
||||||
|
- Flexible macro system
|
||||||
|
- Access to `crates.io`
|
||||||
|
|
||||||
|
Specifically, Dioxus provides us many other assurances:
|
||||||
|
|
||||||
|
- Proper use of immutable datastructures
|
||||||
|
- Guaranteed error handling (so you can sleep easy at night not worrying about `cannot read property of undefined`)
|
||||||
|
- Native performance on mobile
|
||||||
|
- Direct access to system IO
|
||||||
|
|
||||||
|
And much more. Dioxus makes Rust apps just as fast to write as React apps, but affords more robustness, giving your frontend team greater confidence in making big changes in shorter time.
|
||||||
|
|
||||||
|
### Why NOT Dioxus?
|
||||||
|
You shouldn't use Dioxus if:
|
||||||
|
- You don't like the React Hooks approach to frontend
|
||||||
|
- You need a no-std renderer
|
||||||
|
- You want to support browsers where Wasm or asm.js are not supported.
|
||||||
|
|
||||||
# Parity with React
|
# Parity with React
|
||||||
|
|
||||||
|
@ -158,189 +173,6 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
|
||||||
- 👀 = not yet implemented or being worked on
|
- 👀 = not yet implemented or being worked on
|
||||||
- ❓ = not sure if will or can implement
|
- ❓ = not sure if will or can implement
|
||||||
|
|
||||||
## FAQ:
|
|
||||||
|
|
||||||
### Aren't VDOMs just pure overhead? Why not something like Solid or Svelte?
|
|
||||||
Remember: Dioxus is a library - not a compiler like Svelte. Plus, the inner VirtualDOM allows Dioxus to easily port into different runtimes, support SSR, and run remotely in the cloud. VDOMs tend to more ergonomic to work with and feel roughly like natural Rust code. The overhead of Dioxus is **extraordinarily** minimal... sure, there may be some overhead but on an order of magnitude lower than the time required to actually update the page.
|
|
||||||
|
|
||||||
|
|
||||||
### Isn't the overhead for interacting with the DOM from Wasm too much?
|
|
||||||
The overhead layer between Wasm and JS APIs is extremely poorly understood. Rust web benchmarks typically suffer from differences in how Rust and JS cache strings. In Dioxus, we solve most of these issues and our JS Framework Benchmark actually beats the Wasm Bindgen benchmark in many cases. Compared to a "pure vanilla JS" solution, Dioxus adds less than 5% of overhead and takes advantage of batched DOM manipulation.
|
|
||||||
|
|
||||||
### Aren't Wasm binaries too huge to deploy in production?
|
|
||||||
Wasm binary sizes are another poorly understood characteristic of Rust web apps. 50kb of Wasm and 50kb of JS are _not_ made equally. In JS, the code must be downloaded _first_ and _then_ JIT-ted. Just-in-time compiling 50kb of JavaScript takes a while which is why 50kb of JavaScript sounds like a lot! However, with Wasm, the code is downloaded and JIT-ted _simultaneously_ through the magic of streaming compilation. By the time the 50kb of Rust is finished downloading, it is already ready to go. Again, Dioxus beats out many benchmarks with time-to-interactivity.
|
|
||||||
|
|
||||||
For reference, Dioxus `hello-world` clocks in at around 70kb gzip or 60kb brotli, and Dioxus supports SSR.
|
|
||||||
|
|
||||||
### Why hooks? Why not MVC, classes, traits, messages, etc?
|
|
||||||
There are plenty Rust Elm-like frameworks in the world - we were not interested in making another! Instead, we borrowed hooks from React. JS and Rust share many structural similarities, so if you're comfortable with React, then you'll be plenty comfortable with Dioxus.
|
|
||||||
|
|
||||||
### Why a custom DSL? Why not just pure function calls?
|
|
||||||
The `RSX` DSL is _barely_ a DSL. Rustaceans will find the DSL very similar to simply assembling nested structs, but without the syntactical overhead of "Default" everywhere or having to jump through hoops with the builder pattern. Between RSX, HTML, the Raw Factory API, and the NodeBuilder syntax, there's plenty of options to choose from.
|
|
||||||
|
|
||||||
### What are the build times like? Why on earth would I choose Rust instead of JS/TS/Elm?
|
|
||||||
Dioxus builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times.
|
|
||||||
|
|
||||||
### What about Yew/Seed/Sycamore/Dominator/Dodrio/Percy?
|
|
||||||
- Yew and Seed use an Elm-like pattern and don't support SSR or any alternate rendering platforms
|
|
||||||
- Sycamore and Dominator are more like SolidJS/Svelte, requiring no VDOM but has less naturally-Rusty state management
|
|
||||||
- Percy isn't quite mature yet
|
|
||||||
- Dodrio is the spiritual predecessor of Dioxus, but is currently an archived research project without the batteries of Dioxus
|
|
||||||
|
|
||||||
### How do the mobile and desktop renderers work? Is it Electron?
|
|
||||||
Currently, Dioxus uses your device's native WebView library to draw the page. None of your app code is actually running in the WebView thread, so you can access system resources instead of having to go through something like NodeJS. This means your app will use Safari on macOS/iOS, Edge (Chromium) on Windows, and whatever is the default Web Browser for Linux and Android. Because your code is compiled and running natively, performance is not a problem. You will have to use the various "Escape Hatches" to use browser-native APIs (like WebGL) and work around visual differences in how Safari and Chrome render the page.
|
|
||||||
|
|
||||||
In the future, we are interested in using Webrenderer to provide a fully native renderer without having to go through the system WebView library. In practice, Dioxus mobile and desktop are great for CRUD-style apps, but the ergonomic cross-platform APIs (GPS, Camera, etc) are not there yet.
|
|
||||||
|
|
||||||
### Why NOT Dioxus?
|
|
||||||
You shouldn't use Dioxus if:
|
|
||||||
- You don't like the React Hooks approach to frontend
|
|
||||||
- You need a no-std renderer
|
|
||||||
- You want to support browsers where Wasm or asm.js are not supported.
|
|
||||||
|
|
||||||
|
|
||||||
## Show me some examples!
|
|
||||||
|
|
||||||
In our collection of examples, guides, and tutorials, we have:
|
|
||||||
- The book (an introductory course)
|
|
||||||
- The guide (an in-depth analysis of everything in Dioxus)
|
|
||||||
- The reference (a collection of examples with heavy documentation)
|
|
||||||
- The general examples
|
|
||||||
- The platform-specific examples (web, ssr, desktop, mobile, server)
|
|
||||||
|
|
||||||
Here's what a few common tasks look like in Dioxus:
|
|
||||||
|
|
||||||
Nested components with children and internal state:
|
|
||||||
```rust
|
|
||||||
fn App(cx: Context, props: &()) -> Element {
|
|
||||||
cx.render(rsx!( Toggle { "Toggle me" } ))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Props)]
|
|
||||||
struct ToggleProps { children: Element }
|
|
||||||
|
|
||||||
fn Toggle(cx: Context, props: &ToggleProps) -> Element {
|
|
||||||
let mut toggled = use_state(cx, || false);
|
|
||||||
cx.render(rsx!{
|
|
||||||
div {
|
|
||||||
{&props.children}
|
|
||||||
button { onclick: move |_| toggled.set(true),
|
|
||||||
{toggled.and_then(|| "On").or_else(|| "Off")}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Controlled inputs:
|
|
||||||
```rust
|
|
||||||
fn App(cx: Context, props: &()) -> Element {
|
|
||||||
let value = use_state(cx, String::new);
|
|
||||||
cx.render(rsx!(
|
|
||||||
input {
|
|
||||||
"type": "text",
|
|
||||||
value: "{value}",
|
|
||||||
oninput: move |evt| value.set(evt.value.clone())
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Lists and Conditional rendering:
|
|
||||||
```rust
|
|
||||||
fn App(cx: Context, props: &()) -> Element {
|
|
||||||
let list = (0..10).map(|i| {
|
|
||||||
rsx!(li { key: "{i}", "Value: {i}" })
|
|
||||||
});
|
|
||||||
|
|
||||||
let title = match list.len() {
|
|
||||||
0 => rsx!("Not enough"),
|
|
||||||
_ => rsx!("Plenty!"),
|
|
||||||
};
|
|
||||||
|
|
||||||
if should_show {
|
|
||||||
cx.render(rsx!(
|
|
||||||
{title}
|
|
||||||
ul { {list} }
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Tiny components:
|
|
||||||
```rust
|
|
||||||
static App: FC<()> = |cx, _| rsx!(cx, div {"hello world!"});
|
|
||||||
```
|
|
||||||
|
|
||||||
Borrowed prop contents:
|
|
||||||
```rust
|
|
||||||
fn App(cx: Context, props: &()) -> Element {
|
|
||||||
let name = use_state(cx, || String::from("example"));
|
|
||||||
rsx!(cx, Child { title: name.as_str() })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Props)]
|
|
||||||
struct ChildProps<'a> { title: &'a str }
|
|
||||||
|
|
||||||
fn Child(cx: Context, props: &ChildProps) -> Element {
|
|
||||||
rsx!(cx, "Hello {props.title}")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Global State
|
|
||||||
```rust
|
|
||||||
struct GlobalState { name: String }
|
|
||||||
|
|
||||||
fn App(cx: Context, props: &()) -> Element {
|
|
||||||
use_provide_shared_state(cx, || GlobalState { name: String::from("Toby") })
|
|
||||||
rsx!(cx, Leaf {})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Leaf(cx: Context, props: &()) -> Element {
|
|
||||||
let state = use_consume_shared_state::<GlobalState>(cx)?;
|
|
||||||
rsx!(cx, "Hello {state.name}")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Router (inspired by Yew-Router)
|
|
||||||
```rust
|
|
||||||
#[derive(PartialEq, Clone, Hash, Eq, Routable)]
|
|
||||||
enum Route {
|
|
||||||
#[at("/")]
|
|
||||||
Home,
|
|
||||||
#[at("/post/{id}")]
|
|
||||||
Post(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Context, props: &()) -> Element {
|
|
||||||
let route = use_router(cx, Route::parse);
|
|
||||||
cx.render(rsx!(div {
|
|
||||||
{match route {
|
|
||||||
Route::Home => rsx!( Home {} ),
|
|
||||||
Route::Post(id) => rsx!( Post { id: id })
|
|
||||||
}}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Suspense
|
|
||||||
```rust
|
|
||||||
fn App(cx: Context, props: &()) -> Element {
|
|
||||||
let doggo = use_suspense(cx,
|
|
||||||
|| async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::<Response>().await.unwrap() },
|
|
||||||
|response| cx.render(rsx!( img { src: "{response.message}" }))
|
|
||||||
);
|
|
||||||
|
|
||||||
cx.render(rsx!{
|
|
||||||
div {
|
|
||||||
"One doggo coming right up:"
|
|
||||||
{doggo}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -48,3 +48,148 @@ These web-specific examples must be run with `dioxus-cli` using `dioxus develop
|
||||||
| ------- | ------------ |
|
| ------- | ------------ |
|
||||||
| asd | this does |
|
| asd | this does |
|
||||||
| asd | this does |
|
| asd | this does |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Show me some examples!
|
||||||
|
|
||||||
|
In our collection of examples, guides, and tutorials, we have:
|
||||||
|
- The book (an introductory course)
|
||||||
|
- The guide (an in-depth analysis of everything in Dioxus)
|
||||||
|
- The reference (a collection of examples with heavy documentation)
|
||||||
|
- The general examples
|
||||||
|
- The platform-specific examples (web, ssr, desktop, mobile, server)
|
||||||
|
|
||||||
|
Here's what a few common tasks look like in Dioxus:
|
||||||
|
|
||||||
|
Nested components with children and internal state:
|
||||||
|
```rust
|
||||||
|
fn App(cx: Context, props: &()) -> Element {
|
||||||
|
cx.render(rsx!( Toggle { "Toggle me" } ))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Props)]
|
||||||
|
struct ToggleProps { children: Element }
|
||||||
|
|
||||||
|
fn Toggle(cx: Context, props: &ToggleProps) -> Element {
|
||||||
|
let mut toggled = use_state(cx, || false);
|
||||||
|
cx.render(rsx!{
|
||||||
|
div {
|
||||||
|
{&props.children}
|
||||||
|
button { onclick: move |_| toggled.set(true),
|
||||||
|
{toggled.and_then(|| "On").or_else(|| "Off")}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Controlled inputs:
|
||||||
|
```rust
|
||||||
|
fn App(cx: Context, props: &()) -> Element {
|
||||||
|
let value = use_state(cx, String::new);
|
||||||
|
cx.render(rsx!(
|
||||||
|
input {
|
||||||
|
"type": "text",
|
||||||
|
value: "{value}",
|
||||||
|
oninput: move |evt| value.set(evt.value.clone())
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Lists and Conditional rendering:
|
||||||
|
```rust
|
||||||
|
fn App(cx: Context, props: &()) -> Element {
|
||||||
|
let list = (0..10).map(|i| {
|
||||||
|
rsx!(li { key: "{i}", "Value: {i}" })
|
||||||
|
});
|
||||||
|
|
||||||
|
let title = match list.len() {
|
||||||
|
0 => rsx!("Not enough"),
|
||||||
|
_ => rsx!("Plenty!"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_show {
|
||||||
|
cx.render(rsx!(
|
||||||
|
{title}
|
||||||
|
ul { {list} }
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Tiny components:
|
||||||
|
```rust
|
||||||
|
static App: FC<()> = |cx, _| rsx!(cx, div {"hello world!"});
|
||||||
|
```
|
||||||
|
|
||||||
|
Borrowed prop contents:
|
||||||
|
```rust
|
||||||
|
fn App(cx: Context, props: &()) -> Element {
|
||||||
|
let name = use_state(cx, || String::from("example"));
|
||||||
|
rsx!(cx, Child { title: name.as_str() })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Props)]
|
||||||
|
struct ChildProps<'a> { title: &'a str }
|
||||||
|
|
||||||
|
fn Child(cx: Context, props: &ChildProps) -> Element {
|
||||||
|
rsx!(cx, "Hello {props.title}")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Global State
|
||||||
|
```rust
|
||||||
|
struct GlobalState { name: String }
|
||||||
|
|
||||||
|
fn App(cx: Context, props: &()) -> Element {
|
||||||
|
use_provide_shared_state(cx, || GlobalState { name: String::from("Toby") })
|
||||||
|
rsx!(cx, Leaf {})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Leaf(cx: Context, props: &()) -> Element {
|
||||||
|
let state = use_consume_shared_state::<GlobalState>(cx)?;
|
||||||
|
rsx!(cx, "Hello {state.name}")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Router (inspired by Yew-Router)
|
||||||
|
```rust
|
||||||
|
#[derive(PartialEq, Clone, Hash, Eq, Routable)]
|
||||||
|
enum Route {
|
||||||
|
#[at("/")]
|
||||||
|
Home,
|
||||||
|
#[at("/post/{id}")]
|
||||||
|
Post(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn App(cx: Context, props: &()) -> Element {
|
||||||
|
let route = use_router(cx, Route::parse);
|
||||||
|
cx.render(rsx!(div {
|
||||||
|
{match route {
|
||||||
|
Route::Home => rsx!( Home {} ),
|
||||||
|
Route::Post(id) => rsx!( Post { id: id })
|
||||||
|
}}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Suspense
|
||||||
|
```rust
|
||||||
|
fn App(cx: Context, props: &()) -> Element {
|
||||||
|
let doggo = use_suspense(cx,
|
||||||
|
|| async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::<Response>().await.unwrap() },
|
||||||
|
|response| cx.render(rsx!( img { src: "{response.message}" }))
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.render(rsx!{
|
||||||
|
div {
|
||||||
|
"One doggo coming right up:"
|
||||||
|
{doggo}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
32
notes/FAQ.md
Normal file
32
notes/FAQ.md
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
|
||||||
|
### Aren't VDOMs just pure overhead? Why not something like Solid or Svelte?
|
||||||
|
Remember: Dioxus is a library - not a compiler like Svelte. Plus, the inner VirtualDOM allows Dioxus to easily port into different runtimes, support SSR, and run remotely in the cloud. VDOMs tend to more ergonomic to work with and feel roughly like natural Rust code. The overhead of Dioxus is **extraordinarily** minimal... sure, there may be some overhead but on an order of magnitude lower than the time required to actually update the page.
|
||||||
|
|
||||||
|
|
||||||
|
### Isn't the overhead for interacting with the DOM from Wasm too much?
|
||||||
|
The overhead layer between Wasm and JS APIs is extremely poorly understood. Rust web benchmarks typically suffer from differences in how Rust and JS cache strings. In Dioxus, we solve most of these issues and our JS Framework Benchmark actually beats the Wasm Bindgen benchmark in many cases. Compared to a "pure vanilla JS" solution, Dioxus adds less than 5% of overhead and takes advantage of batched DOM manipulation.
|
||||||
|
|
||||||
|
### Aren't Wasm binaries too huge to deploy in production?
|
||||||
|
Wasm binary sizes are another poorly understood characteristic of Rust web apps. 50kb of Wasm and 50kb of JS are _not_ made equally. In JS, the code must be downloaded _first_ and _then_ JIT-ted. Just-in-time compiling 50kb of JavaScript takes a while which is why 50kb of JavaScript sounds like a lot! However, with Wasm, the code is downloaded and JIT-ted _simultaneously_ through the magic of streaming compilation. By the time the 50kb of Rust is finished downloading, it is already ready to go. Again, Dioxus beats out many benchmarks with time-to-interactivity.
|
||||||
|
|
||||||
|
For reference, Dioxus `hello-world` clocks in at around 70kb gzip or 60kb brotli, and Dioxus supports SSR.
|
||||||
|
|
||||||
|
### Why hooks? Why not MVC, classes, traits, messages, etc?
|
||||||
|
There are plenty Rust Elm-like frameworks in the world - we were not interested in making another! Instead, we borrowed hooks from React. JS and Rust share many structural similarities, so if you're comfortable with React, then you'll be plenty comfortable with Dioxus.
|
||||||
|
|
||||||
|
### Why a custom DSL? Why not just pure function calls?
|
||||||
|
The `RSX` DSL is _barely_ a DSL. Rustaceans will find the DSL very similar to simply assembling nested structs, but without the syntactical overhead of "Default" everywhere or having to jump through hoops with the builder pattern. Between RSX, HTML, the Raw Factory API, and the NodeBuilder syntax, there's plenty of options to choose from.
|
||||||
|
|
||||||
|
### What are the build times like? Why on earth would I choose Rust instead of JS/TS/Elm?
|
||||||
|
Dioxus builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times.
|
||||||
|
|
||||||
|
### What about Yew/Seed/Sycamore/Dominator/Dodrio/Percy?
|
||||||
|
- Yew and Seed use an Elm-like pattern and don't support SSR or any alternate rendering platforms
|
||||||
|
- Sycamore and Dominator are more like SolidJS/Svelte, requiring no VDOM but has less naturally-Rusty state management
|
||||||
|
- Percy isn't quite mature yet
|
||||||
|
- Dodrio is the spiritual predecessor of Dioxus, but is currently an archived research project without the batteries of Dioxus
|
||||||
|
|
||||||
|
### How do the mobile and desktop renderers work? Is it Electron?
|
||||||
|
Currently, Dioxus uses your device's native WebView library to draw the page. None of your app code is actually running in the WebView thread, so you can access system resources instead of having to go through something like NodeJS. This means your app will use Safari on macOS/iOS, Edge (Chromium) on Windows, and whatever is the default Web Browser for Linux and Android. Because your code is compiled and running natively, performance is not a problem. You will have to use the various "Escape Hatches" to use browser-native APIs (like WebGL) and work around visual differences in how Safari and Chrome render the page.
|
||||||
|
|
||||||
|
In the future, we are interested in using Webrenderer to provide a fully native renderer without having to go through the system WebView library. In practice, Dioxus mobile and desktop are great for CRUD-style apps, but the ergonomic cross-platform APIs (GPS, Camera, etc) are not there yet.
|
|
@ -49,19 +49,17 @@ impl ToTokens for CallBody {
|
||||||
match &self.custom_context {
|
match &self.custom_context {
|
||||||
// The `in cx` pattern allows directly rendering
|
// The `in cx` pattern allows directly rendering
|
||||||
Some(ident) => out_tokens.append_all(quote! {
|
Some(ident) => out_tokens.append_all(quote! {
|
||||||
#ident.render(NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode {
|
#ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
|
||||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||||
#inner
|
#inner
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
// Otherwise we just build the LazyNode wrapper
|
// Otherwise we just build the LazyNode wrapper
|
||||||
None => out_tokens.append_all(quote! {
|
None => out_tokens.append_all(quote! {
|
||||||
{
|
LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
|
||||||
NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode {
|
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
#inner
|
||||||
#inner
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,6 @@ pub struct DiffState<'bump> {
|
||||||
scopes: &'bump ScopeArena,
|
scopes: &'bump ScopeArena,
|
||||||
pub mutations: Mutations<'bump>,
|
pub mutations: Mutations<'bump>,
|
||||||
pub(crate) stack: DiffStack<'bump>,
|
pub(crate) stack: DiffStack<'bump>,
|
||||||
pub seen_scopes: FxHashSet<ScopeId>,
|
|
||||||
pub force_diff: bool,
|
pub force_diff: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +118,6 @@ impl<'bump> DiffState<'bump> {
|
||||||
scopes,
|
scopes,
|
||||||
mutations: Mutations::new(),
|
mutations: Mutations::new(),
|
||||||
stack: DiffStack::new(),
|
stack: DiffStack::new(),
|
||||||
seen_scopes: Default::default(),
|
|
||||||
force_diff: false,
|
force_diff: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -492,7 +490,7 @@ impl<'bump> DiffState<'bump> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, insert this scope as a seen node.
|
// Finally, insert this scope as a seen node.
|
||||||
self.seen_scopes.insert(new_idx);
|
self.mutations.dirty_scopes.insert(new_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_linked_node(&mut self, link: &'bump VPortal) {
|
fn create_linked_node(&mut self, link: &'bump VPortal) {
|
||||||
|
|
|
@ -35,6 +35,14 @@ enum StackNodeStorage<'a, 'b> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> LazyNodes<'a, 'b> {
|
impl<'a, 'b> LazyNodes<'a, 'b> {
|
||||||
|
|
||||||
|
pub fn new_some<F>(_val: F) -> Option<Self>
|
||||||
|
where
|
||||||
|
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
|
||||||
|
{
|
||||||
|
Some(Self::new(_val))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new<F>(_val: F) -> Self
|
pub fn new<F>(_val: F) -> Self
|
||||||
where
|
where
|
||||||
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
|
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
|
||||||
|
@ -221,7 +229,7 @@ fn it_works() {
|
||||||
|
|
||||||
let factory = NodeFactory { bump: &bump };
|
let factory = NodeFactory { bump: &bump };
|
||||||
|
|
||||||
let caller = NodeFactory::annotate_lazy(|f| {
|
let caller = LazyNodes::new_some(|f| {
|
||||||
//
|
//
|
||||||
f.text(format_args!("hello world!"))
|
f.text(format_args!("hello world!"))
|
||||||
})
|
})
|
||||||
|
@ -254,7 +262,7 @@ fn it_drops() {
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let val = val.clone();
|
let val = val.clone();
|
||||||
|
|
||||||
NodeFactory::annotate_lazy(move |f| {
|
LazyNodes::new_some(move |f| {
|
||||||
log::debug!("hell closure");
|
log::debug!("hell closure");
|
||||||
let inner = DropInner { id: i };
|
let inner = DropInner { id: i };
|
||||||
f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
|
f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
|
||||||
|
@ -262,7 +270,7 @@ fn it_drops() {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
NodeFactory::annotate_lazy(|f| {
|
LazyNodes::new_some(|f| {
|
||||||
log::debug!("main closure");
|
log::debug!("main closure");
|
||||||
f.fragment_from_iter(it)
|
f.fragment_from_iter(it)
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,6 +14,7 @@ use std::{any::Any, fmt::Debug};
|
||||||
/// Mutations are the only link between the RealDOM and the VirtualDOM.
|
/// Mutations are the only link between the RealDOM and the VirtualDOM.
|
||||||
pub struct Mutations<'a> {
|
pub struct Mutations<'a> {
|
||||||
pub edits: Vec<DomEdit<'a>>,
|
pub edits: Vec<DomEdit<'a>>,
|
||||||
|
pub dirty_scopes: FxHashSet<ScopeId>,
|
||||||
pub refs: Vec<NodeRefMutation<'a>>,
|
pub refs: Vec<NodeRefMutation<'a>>,
|
||||||
pub effects: Vec<&'a dyn FnMut()>,
|
pub effects: Vec<&'a dyn FnMut()>,
|
||||||
}
|
}
|
||||||
|
@ -103,6 +104,7 @@ pub enum DomEdit<'bump> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use fxhash::FxHashSet;
|
||||||
use DomEdit::*;
|
use DomEdit::*;
|
||||||
|
|
||||||
impl<'a> Mutations<'a> {
|
impl<'a> Mutations<'a> {
|
||||||
|
@ -110,6 +112,7 @@ impl<'a> Mutations<'a> {
|
||||||
Self {
|
Self {
|
||||||
edits: Vec::new(),
|
edits: Vec::new(),
|
||||||
refs: Vec::new(),
|
refs: Vec::new(),
|
||||||
|
dirty_scopes: Default::default(),
|
||||||
effects: Vec::new(),
|
effects: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -732,12 +732,6 @@ impl<'a> NodeFactory<'a> {
|
||||||
node: unsafe { std::mem::transmute(ptr) },
|
node: unsafe { std::mem::transmute(ptr) },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn annotate_lazy<'z, 'b>(
|
|
||||||
f: impl FnOnce(NodeFactory<'z>) -> VNode<'z> + 'b,
|
|
||||||
) -> Option<LazyNodes<'z, 'b>> {
|
|
||||||
Some(LazyNodes::new(f))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for NodeFactory<'_> {
|
impl Debug for NodeFactory<'_> {
|
||||||
|
|
|
@ -44,6 +44,7 @@ pub type Context<'a> = &'a Scope;
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
pub(crate) parent_scope: Option<*mut Scope>,
|
pub(crate) parent_scope: Option<*mut Scope>,
|
||||||
|
|
||||||
|
// parent element I think?
|
||||||
pub(crate) container: ElementId,
|
pub(crate) container: ElementId,
|
||||||
|
|
||||||
pub(crate) our_arena_idx: ScopeId,
|
pub(crate) our_arena_idx: ScopeId,
|
||||||
|
@ -79,8 +80,8 @@ pub struct SelfReferentialItems<'a> {
|
||||||
|
|
||||||
/// A component's unique identifier.
|
/// A component's unique identifier.
|
||||||
///
|
///
|
||||||
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
|
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM and across time. ScopeIDs will never be reused
|
||||||
/// unmounted, then the `ScopeId` will be reused for a new component.
|
/// once a component has been unmounted.
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
pub struct ScopeId(pub usize);
|
pub struct ScopeId(pub usize);
|
||||||
|
@ -141,7 +142,7 @@ impl Scope {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
|
/// let mut dom = VirtualDom::new(|cx, props| cx.render(rsx!{ div {} }));
|
||||||
/// dom.rebuild();
|
/// dom.rebuild();
|
||||||
///
|
///
|
||||||
/// let base = dom.base_scope();
|
/// let base = dom.base_scope();
|
||||||
|
@ -161,7 +162,7 @@ impl Scope {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
|
/// let mut dom = VirtualDom::new(|cx, props| cx.render(rsx!{ div {} }));
|
||||||
/// dom.rebuild();
|
/// dom.rebuild();
|
||||||
///
|
///
|
||||||
/// let base = dom.base_scope();
|
/// let base = dom.base_scope();
|
||||||
|
@ -180,7 +181,7 @@ impl Scope {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
|
/// let mut dom = VirtualDom::new(|cx, props| cx.render(rsx!{ div {} }));
|
||||||
/// dom.rebuild();
|
/// dom.rebuild();
|
||||||
/// let base = dom.base_scope();
|
/// let base = dom.base_scope();
|
||||||
///
|
///
|
||||||
|
|
|
@ -347,12 +347,6 @@ impl VirtualDom {
|
||||||
pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
|
pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
|
||||||
let mut committed_mutations = vec![];
|
let mut committed_mutations = vec![];
|
||||||
|
|
||||||
log::debug!(
|
|
||||||
"Working with deadline. \nDirty scopes: {:?}. \nPending messages: {:?}",
|
|
||||||
self.dirty_scopes,
|
|
||||||
self.pending_messages
|
|
||||||
);
|
|
||||||
|
|
||||||
while !self.dirty_scopes.is_empty() {
|
while !self.dirty_scopes.is_empty() {
|
||||||
let scopes = &self.scopes;
|
let scopes = &self.scopes;
|
||||||
let mut diff_state = DiffState::new(scopes);
|
let mut diff_state = DiffState::new(scopes);
|
||||||
|
@ -370,7 +364,6 @@ impl VirtualDom {
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(scopeid) = self.dirty_scopes.pop() {
|
if let Some(scopeid) = self.dirty_scopes.pop() {
|
||||||
log::debug!("Analyzing dirty scope {:?}", scopeid);
|
|
||||||
if !ran_scopes.contains(&scopeid) {
|
if !ran_scopes.contains(&scopeid) {
|
||||||
ran_scopes.insert(scopeid);
|
ran_scopes.insert(scopeid);
|
||||||
|
|
||||||
|
@ -379,7 +372,6 @@ impl VirtualDom {
|
||||||
self.scopes.wip_head(&scopeid),
|
self.scopes.wip_head(&scopeid),
|
||||||
self.scopes.fin_head(&scopeid),
|
self.scopes.fin_head(&scopeid),
|
||||||
);
|
);
|
||||||
log::debug!("Diffing old: {:?}, new: {:?}", old, new);
|
|
||||||
diff_state.stack.push(DiffInstruction::Diff { new, old });
|
diff_state.stack.push(DiffInstruction::Diff { new, old });
|
||||||
diff_state.stack.scope_stack.push(scopeid);
|
diff_state.stack.scope_stack.push(scopeid);
|
||||||
|
|
||||||
|
@ -390,20 +382,16 @@ impl VirtualDom {
|
||||||
}
|
}
|
||||||
|
|
||||||
if diff_state.work(&mut deadline) {
|
if diff_state.work(&mut deadline) {
|
||||||
let DiffState {
|
let DiffState { mutations, .. } = diff_state;
|
||||||
mutations,
|
|
||||||
seen_scopes,
|
|
||||||
..
|
|
||||||
} = diff_state;
|
|
||||||
|
|
||||||
for scope in seen_scopes {
|
for scope in &mutations.dirty_scopes {
|
||||||
self.dirty_scopes.remove(&scope);
|
self.dirty_scopes.remove(scope);
|
||||||
}
|
}
|
||||||
log::debug!("Working generated mutations: {:?}", mutations);
|
|
||||||
|
|
||||||
committed_mutations.push(mutations);
|
committed_mutations.push(mutations);
|
||||||
} else {
|
} else {
|
||||||
log::debug!("Could not finish work in time");
|
log::debug!("Could not finish work in time");
|
||||||
|
|
||||||
// leave the work in an incomplete state
|
// leave the work in an incomplete state
|
||||||
return committed_mutations;
|
return committed_mutations;
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,6 +173,6 @@ pub fn use_provide_state<'a, T: 'static>(cx: Context<'a>, f: impl FnOnce() -> T)
|
||||||
});
|
});
|
||||||
cx.provide_state(state)
|
cx.provide_state(state)
|
||||||
},
|
},
|
||||||
|inner| {},
|
|_inner| {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use dioxus_core::Context;
|
use dioxus_core::Context;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Borrow,
|
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
@ -42,7 +41,7 @@ pub fn use_coroutine<'a, F: Future<Output = ()> + 'a>(
|
||||||
} else {
|
} else {
|
||||||
// make sure to drop the old future
|
// make sure to drop the old future
|
||||||
if let Some(fut) = state.fut.borrow_mut().take() {
|
if let Some(fut) = state.fut.borrow_mut().take() {
|
||||||
//
|
drop(fut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CoroutineHandle { cx, inner: state }
|
CoroutineHandle { cx, inner: state }
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use dioxus_core as dioxus;
|
use dioxus_core as dioxus;
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_core_macro::{format_args_f, rsx, Props};
|
use dioxus_core_macro::{rsx, Props};
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
use wasm_bindgen::{JsCast, JsValue};
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
use web_sys::{window, Event};
|
use web_sys::{window, Event};
|
||||||
|
@ -128,9 +128,7 @@ pub fn use_router<R: Routable>(cx: Context, mut parse: impl FnMut(&str) -> R + '
|
||||||
}
|
}
|
||||||
let history = state.history_service.borrow();
|
let history = state.history_service.borrow();
|
||||||
|
|
||||||
todo!()
|
state.historic_routes.last().unwrap()
|
||||||
// history.state.historic_routes.last().unwrap()
|
|
||||||
//
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
219
src/lib.rs
219
src/lib.rs
|
@ -4,166 +4,124 @@
|
||||||
//! <strong>A concurrent, functional, virtual DOM for Rust</strong>
|
//! <strong>A concurrent, functional, virtual DOM for Rust</strong>
|
||||||
//! </p>
|
//! </p>
|
||||||
//! </div>
|
//! </div>
|
||||||
//! Dioxus: a concurrent, functional, reactive virtual dom for any renderer in Rust.
|
|
||||||
//!
|
//!
|
||||||
//! This crate aims to maintain a hook-based, renderer-agnostic framework for cross-platform UI development.
|
//! # Resources
|
||||||
//!
|
//!
|
||||||
//! ## Overview and Goals
|
//! This overview is provides a brief introduction to Dioxus. For a more in-depth guide, make sure to check out:
|
||||||
//! Dioxus' ultimate goal is to save you from writing new code when bringing your application to new platforms. We forsee
|
//! - [Getting Started](https://dioxuslabs.com/getting-started)
|
||||||
//! a future where WebApps, Mobile Apps, Desktop Apps, and even AR apps can be written in the same language, ecosystem,
|
//! - [Book](https://dioxuslabs.com/book)
|
||||||
//! and leverage the same platform-agnostic libraries.
|
//! - [Reference](https://dioxuslabs.com/refernce-guide)
|
||||||
|
|
||||||
//!
|
//!
|
||||||
//! In this aim we chose to use a variety of techniques:
|
//! # Overview and Goals
|
||||||
//! - We use a VirtualDOM to abstract the true renderer from application logic.
|
|
||||||
//! - We use functions as components to limit the API churn for greater stability.
|
|
||||||
//! - We use hooks as state to allow reusable logic across the whole ecosystem.
|
|
||||||
//! - We support an extensible and compile-time safe DSL for building interfaces.
|
|
||||||
//!
|
//!
|
||||||
//! Our guiding stars (in order of priority):
|
//! Dioxus makes it easy to quickly build complex user interfaces with Rust. Any Dioxus app can run in the web browser,
|
||||||
//! - Ergonomics
|
//! as a desktop app, as a mobile app, or anywhere else provided you build the right renderer.
|
||||||
//! - Reusability
|
//!
|
||||||
//! - Speed and memory efficiency
|
//! Dioxus is heavily inspired by React, supporting many of the same concepts:
|
||||||
//! - Safety
|
//!
|
||||||
|
//! - Hooks for state
|
||||||
|
//! - VirtualDom & diffing
|
||||||
|
//! - Concurrency & asynchronous rendering
|
||||||
|
//! - JSX-like templating syntax
|
||||||
|
//!
|
||||||
|
//! If you know React, then you know Dioxus.
|
||||||
|
//!
|
||||||
|
//! Dioxus is *substantially* faster than many of the other Rust UI libraries (Yew/Percy) and is *significantly* faster
|
||||||
|
//! than React, competitve with InfernoJS and frameworks like Svelte/SolidJS.
|
||||||
|
//!
|
||||||
|
//! ## Brief Overview
|
||||||
|
//!
|
||||||
|
//! All Dioxus apps are built by composing functions that take in a `Scope` and `Properties` and return an `Element`. A `Scope` holds
|
||||||
|
//! relevant state data for the the currently-rendered component.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use dioxus::prelude::*;
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! dioxus::desktop::launch(App);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn App(cx: Scope, props: &()) -> Element {
|
||||||
|
//! let mut count = use_state(cx, || 0);
|
||||||
|
//!
|
||||||
|
//! cx.render(rsx!(
|
||||||
|
//! div { "Count: {count}" }
|
||||||
|
//! button { onclick: move |_| count += 1, "Increment" }
|
||||||
|
//! button { onclick: move |_| count -= 1, "Decrement" }
|
||||||
|
//! ))
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Components
|
//! ## Components
|
||||||
//! The base unit of Dioxus is the `component`. Components can be easily created from just a function - no traits or
|
|
||||||
//! proc macros required:
|
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! We can compose these function components to build a complex app. Each new component we design must take some Properties.
|
||||||
//! use dioxus::prelude::*;
|
//! For components with no explicit properties, we can use the `()` type. In Dioxus, all properties are memoized by default!
|
||||||
//!
|
//!
|
||||||
//! fn App(cx: Context, props: &()) -> Element {
|
//! ```rust
|
||||||
|
//! fn App(cx: Scope, props &()) -> Element {
|
||||||
//! cx.render(rsx!(
|
//! cx.render(rsx!(
|
||||||
//! div {"hello world"}
|
//! Header {
|
||||||
|
//! title: "My App",
|
||||||
|
//! color: "red",
|
||||||
|
//! }
|
||||||
//! ))
|
//! ))
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used
|
|
||||||
//! and what properties can be used to specify it in the VNode output. Components without properties may be generic over
|
|
||||||
//! `()`, and components with properties must declare their properties as a struct:
|
|
||||||
//!
|
//!
|
||||||
|
//! Our `Header` component takes in a `title` and a `color` property, which we delcare on an explicit `HeaderProps` struct.
|
||||||
//! ```
|
//! ```
|
||||||
|
//! // The `Props` derive macro lets us add additional functionality to how props are interpreted.
|
||||||
//! #[derive(Props, PartialEq)]
|
//! #[derive(Props, PartialEq)]
|
||||||
//! struct AppProps {
|
//! struct HeaderProps {
|
||||||
//! name: String
|
//! title: String,
|
||||||
|
//! color: String,
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! fn App(cx: Context, props: &AppProps) -> Element {
|
//! fn Header(cx: Scope, props: &HeaderProps) -> Element {
|
||||||
//! cx.render(rsx!(
|
//! cx.render(rsx!(
|
||||||
//! div { "Hello {props.name}!" }
|
//! div {
|
||||||
|
//! background_color: "{props.color}"
|
||||||
|
//! h1 { "{props.title}" }
|
||||||
|
//! }
|
||||||
//! ))
|
//! ))
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Props that are valid for the `'pub static` lifetime automatically get memoized by Diouxs. This means the component won't
|
//! ## Hooks
|
||||||
//! re-render if its Props didn't change. However, Props that borrow data from their parent cannot be safely memoized, and
|
|
||||||
//! will always re-render if their parent changes. To borrow data from a parent, your component needs to add explicit lifetimes,
|
|
||||||
//! otherwise Rust will get confused about whether data is borrowed from either Props or Context. Since Dioxus manages
|
|
||||||
//! these lifetimes internally, Context and your Props must share the same lifetime:
|
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! While components are reusable forms of UI elements, hooks are reusable forms of logic. The details of hooks are
|
||||||
//! #[derive(Props)]
|
//! somewhat complicated. In essence, hooks let us save state between renders of our components and reuse the accompanying
|
||||||
//! struct Props<'a> {
|
//! logic across different apps.
|
||||||
//! name: &'a str
|
|
||||||
//! }
|
|
||||||
//!
|
//!
|
||||||
//! fn Example(cx: Context, props: &AppProps) -> Element {
|
//! Hooks are simply composition of other hooks. To create our first hook we can create a simple function that takes in
|
||||||
//! cx.render(rsx!(
|
//! an Scope. We can then call `use_hook` on the `Scope` to get a mutable reference to the stored value.
|
||||||
//! div { "Hello {props.name}!" }
|
//!
|
||||||
//! ))
|
//! ```rust
|
||||||
|
//! fn use_say_hello(cx: Scope) -> &mut String {
|
||||||
|
//! cx.use_hook(|_| "Hello".to_string(), |hook| hook)
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! To use custom properties for components, you'll need to derive the `Props` trait for your properties. This trait
|
//! If you want to extend Dioxus with some new functionality, you'll probably want to implement a new hook.
|
||||||
//! exposes a compile-time correct builder pattern (similar to typed-builder) that can be used in the `rsx!` macro to
|
|
||||||
//! build components. Component props may have default fields notated by the `Default` attribute:
|
|
||||||
//!
|
//!
|
||||||
//! ```
|
|
||||||
//! #[derive(Props)]
|
|
||||||
//! struct Props {
|
|
||||||
//! name: String
|
|
||||||
//!
|
//!
|
||||||
//! #[props(default = false)]
|
//! ## Features
|
||||||
//! checked: bool,
|
|
||||||
//!
|
//!
|
||||||
//! #[props(default, into))]
|
//! This overview doesn't cover everything. Make sure to check out the tutorial and reference guide on the official
|
||||||
//! title: Option<String>
|
//! website for more details.
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//!
|
//!
|
||||||
//! These flags roughly follow that of typed-builder, though tweaked to support the `Props` usecase.
|
//! Beyond this overview, Dioxus supports:
|
||||||
//!
|
//! - Server-side rendering
|
||||||
//! ## Hooks and State
|
//! - Concurrent rendering (with async support)
|
||||||
//! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component.
|
//! - Web/Desktop/Mobile support
|
||||||
//!
|
//! - Pre-rendering and rehydration
|
||||||
//! ```
|
//! - Fragments, Portals, and Suspense
|
||||||
//! pub pub static Example: FC<()> = |cx, props|{
|
//! - Inline-styles
|
||||||
//! let mut val = use_state(cx, || 0);
|
//! - Custom event handlers
|
||||||
//! cx.render(rsx!(
|
//! - Custom elements
|
||||||
//! button { onclick: move |_| val += 1 }
|
//! - Basic fine-grained reactivity (IE SolidJS/Svelte)
|
||||||
//! ))
|
//! - and more!
|
||||||
//! }
|
|
||||||
//! ````
|
|
||||||
//!
|
|
||||||
//! As a building block for hooks, Dioxus provides the `use_hook` method on Context that stores a provided value in a
|
|
||||||
//! list of other values. Whenever `use_hook` is called, the next hook value in the list is returned.
|
|
||||||
//! ```
|
|
||||||
//! fn my_hook(cx: Context) -> &String {
|
|
||||||
//! cx.use_hook(
|
|
||||||
//! // Initializer stores a value
|
|
||||||
//! |hook_idx| String::new("stored_data"),
|
|
||||||
//!
|
|
||||||
//! // Runner returns the hook value every time the component is rendered
|
|
||||||
//! |hook| &*hook,
|
|
||||||
//! )
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//! Under the hood, hooks store their data in a list of `Box<dyn Any>`. The first render defines the layout of these
|
|
||||||
//! list, and on each subsequent render, each `use_hook` call accesses its corresponding list item. If a hook
|
|
||||||
//! accesses the wrong index, `use_hook` will panic when trying to downcast `Any` to your type, and your app will crash.
|
|
||||||
//! These types of errors can be easily mitigated by following the rules of hooks:
|
|
||||||
//!
|
|
||||||
//! - Don’t call Hooks inside loops, conditions, or nested functions
|
|
||||||
//! - Don't call hooks in changing order between renders
|
|
||||||
//!
|
|
||||||
//! Hooks provide a very powerful way to reuse stateful logic between components, simplify large complex components,
|
|
||||||
//! and adopt more clear context subscription patterns to make components easier to read. The mechanics of hooks in Dioxus
|
|
||||||
//! shares a great amount of similarity with React's hooks and there are many guides to hooks in React online.
|
|
||||||
//!
|
|
||||||
//! ## Supported Renderers
|
|
||||||
//! Instead of being tightly coupled to a platform, browser, or toolkit, Dioxus implements a VirtualDOM object which
|
|
||||||
//! can be consumed to draw the UI. The Dioxus VDOM is reactive and easily consumable by 3rd-party renderers via
|
|
||||||
//! the `RealDom` trait. See [Implementing a Renderer](docs/8-custom-renderer.md), the `StringRenderer`, and `WebSys` render implementations for a template
|
|
||||||
//! on how to implement your own custom renderer. We provide 1st-class support for these renderers:
|
|
||||||
//!
|
|
||||||
//! - dioxus-desktop (via WebView)
|
|
||||||
//! - dioxus-web (via WebSys)
|
|
||||||
//! - dioxus-ssr (via StringRenderer)
|
|
||||||
//! - dioxus-liveview (SSR + WebSys)
|
|
||||||
//!
|
|
||||||
//! In the main `dioxus` crate, these are all accessible through configuration flags.
|
|
||||||
//!
|
|
||||||
//! ## Rendering to the Web
|
|
||||||
//!
|
|
||||||
//! Most dioxus apps will be initialized in roughly the same way. The `launch` method in `web` will immediately start a
|
|
||||||
//! VirtualDOM and await it using `wasm_bindgen_futures`.
|
|
||||||
//!
|
|
||||||
//! An example app that starts a websys app and internally awaits works as follows:
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! use dioxus::prelude::*;
|
|
||||||
//! fn main() {
|
|
||||||
//! dioxus::web::launch(Example);
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! pub pub static Example: FC<()> = |cx, props|{
|
|
||||||
//! cx.render(rsx! {
|
|
||||||
//! div { "Hello World!" }
|
|
||||||
//! })
|
|
||||||
//! };
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! In reality, you'll want to integrate analytics, logging, crash-protection and more.
|
|
||||||
|
|
||||||
// Just a heads-up, the core functionality of dioxus rests in Dioxus-Core. This crate just wraps a bunch of utilities
|
// Just a heads-up, the core functionality of dioxus rests in Dioxus-Core. This crate just wraps a bunch of utilities
|
||||||
// together and exports their namespaces to something predicatble.
|
// together and exports their namespaces to something predicatble.
|
||||||
|
@ -188,15 +146,12 @@ pub use dioxus_desktop as desktop;
|
||||||
#[cfg(feature = "router")]
|
#[cfg(feature = "router")]
|
||||||
pub use dioxus_router as router;
|
pub use dioxus_router as router;
|
||||||
|
|
||||||
pub mod debug {}
|
|
||||||
|
|
||||||
pub mod events {
|
pub mod events {
|
||||||
#[cfg(feature = "html")]
|
#[cfg(feature = "html")]
|
||||||
pub use dioxus_html::{on::*, KeyCode};
|
pub use dioxus_html::{on::*, KeyCode};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
//! A glob import that includes helper types like FC, rsx!, html!, and required traits
|
|
||||||
pub use dioxus_core::prelude::*;
|
pub use dioxus_core::prelude::*;
|
||||||
pub use dioxus_core_macro::{format_args_f, rsx, Props, Routable};
|
pub use dioxus_core_macro::{format_args_f, rsx, Props, Routable};
|
||||||
pub use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
pub use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||||
|
|
Loading…
Reference in a new issue