mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-11 15:07:08 +00:00
Merge branch 'master' into jk/use-future-leak
This commit is contained in:
commit
a46bd8d6d7
182 changed files with 6075 additions and 2716 deletions
14
.docker/Dockerfile_base_test_image
Normal file
14
.docker/Dockerfile_base_test_image
Normal file
|
@ -0,0 +1,14 @@
|
|||
FROM rust:1.58-buster
|
||||
|
||||
RUN apt update
|
||||
RUN apt install -y \
|
||||
libglib2.0-dev \
|
||||
libgtk-3-dev \
|
||||
libsoup2.4-dev \
|
||||
libappindicator3-dev \
|
||||
libwebkit2gtk-4.0-dev \
|
||||
firefox-esr \
|
||||
# for Tarpaulin code coverage
|
||||
liblzma-dev binutils-dev libcurl4-openssl-dev libdw-dev libelf-dev
|
||||
|
||||
CMD ["exit"]
|
7
.docker/Dockerfile_code_coverage
Normal file
7
.docker/Dockerfile_code_coverage
Normal file
|
@ -0,0 +1,7 @@
|
|||
FROM dioxus-test-image
|
||||
|
||||
WORKDIR /run_test
|
||||
RUN cargo install cargo-tarpaulin
|
||||
RUN cargo cache -a
|
||||
|
||||
ENTRYPOINT [ "bash" ]
|
|
@ -1,20 +1,8 @@
|
|||
FROM rust:1.58-buster
|
||||
FROM dioxus-base-test-image
|
||||
|
||||
RUN apt update
|
||||
RUN apt install -y \
|
||||
libglib2.0-dev \
|
||||
libgtk-3-dev \
|
||||
libsoup2.4-dev \
|
||||
libappindicator3-dev \
|
||||
libwebkit2gtk-4.0-dev \
|
||||
firefox-esr
|
||||
# for kcov and Tarpaulin
|
||||
#liblzma-dev binutils-dev libcurl4-openssl-dev libdw-dev libelf-dev
|
||||
|
||||
RUN cargo install cargo-make --debug
|
||||
# for test coverage
|
||||
#RUN cargo install cargo-tarpaulin
|
||||
# clean up a bit
|
||||
RUN cargo install cargo-binstall
|
||||
RUN cargo install cargo-make
|
||||
RUN cargo install wasm-pack
|
||||
RUN cargo install cargo-cache && cargo cache -a
|
||||
|
||||
CMD ["exit"]
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
FROM dioxus-base-test-image
|
||||
FROM dioxus-pre-test
|
||||
|
||||
RUN mkdir run_test
|
||||
COPY tmp /run_test
|
||||
WORKDIR /run_test
|
||||
RUN cargo make tests
|
||||
RUN cargo cache -a
|
||||
|
||||
CMD ["exit"]
|
||||
|
|
|
@ -12,9 +12,16 @@ function run_script {
|
|||
rsync -a --progress ../ tmp --exclude target --exclude docker
|
||||
|
||||
# build base image
|
||||
docker build -f Dockerfile_pre_test -t dioxus-base-test-image .
|
||||
docker build -f Dockerfile_base_test_image -t dioxus-base-test-image .
|
||||
docker build -f Dockerfile_pre_test -t dioxus-pre-test .
|
||||
# run test
|
||||
docker build -f Dockerfile_test -t dioxus-test-image .
|
||||
# code coverage
|
||||
docker build -f Dockerfile_code_coverage -t dioxus-code-coverage .
|
||||
|
||||
# exec test coverage
|
||||
cd .. && \
|
||||
echo "rustup default nightly && cargo +nightly tarpaulin --verbose --all-features --tests --workspace --exclude core-macro --timeout 120 --out Html" | docker run -i --rm --security-opt seccomp=unconfined -v "/home/elios/project/prs/dioxus/:/run_test" dioxus-code-coverage
|
||||
|
||||
# clean up
|
||||
rm -rf tmp
|
||||
|
|
37
.github/workflows/docs.yml
vendored
Normal file
37
.github/workflows/docs.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
name: github pages
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- docs/**
|
||||
- .github/workflows/docs.yml
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: docs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup mdBook
|
||||
uses: peaceiris/actions-mdbook@v1
|
||||
with:
|
||||
mdbook-version: "0.4.10"
|
||||
|
||||
- name: Build
|
||||
run: cd docs &&
|
||||
cd guide && mdbook build -d ../nightly/guide && cd .. &&
|
||||
cd reference && mdbook build -d ../nightly/reference && cd .. &&
|
||||
cd router && mdbook build -d ../nightly/router && cd ..
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@v4.2.3
|
||||
with:
|
||||
branch: gh-pages # The branch the action should deploy to.
|
||||
folder: docs/nightly # The folder the action should deploy.
|
||||
target-folder: docs/nightly
|
||||
repository-name: dioxuslabs/docsite
|
||||
clean: false
|
||||
token: ${{ secrets.DEPLOY_KEY }} # let's pretend I don't need it for now
|
27
.github/workflows/macos.yml
vendored
27
.github/workflows/macos.yml
vendored
|
@ -1,9 +1,32 @@
|
|||
on: [push, pull_request]
|
||||
|
||||
name: macOS tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- packages/**
|
||||
- examples/**
|
||||
- src/**
|
||||
- .github/**
|
||||
- lib.rs
|
||||
- Cargo.toml
|
||||
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- packages/**
|
||||
- examples/**
|
||||
- src/**
|
||||
- .github/**
|
||||
- lib.rs
|
||||
- Cargo.toml
|
||||
|
||||
jobs:
|
||||
test:
|
||||
if: github.event.pull_request.draft == false
|
||||
name: Test Suite
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
|
|
51
.github/workflows/main.yml
vendored
51
.github/workflows/main.yml
vendored
|
@ -1,9 +1,32 @@
|
|||
on: [push, pull_request]
|
||||
|
||||
name: Rust CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- packages/**
|
||||
- examples/**
|
||||
- src/**
|
||||
- .github/**
|
||||
- lib.rs
|
||||
- Cargo.toml
|
||||
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- packages/**
|
||||
- examples/**
|
||||
- src/**
|
||||
- .github/**
|
||||
- lib.rs
|
||||
- Cargo.toml
|
||||
|
||||
jobs:
|
||||
check:
|
||||
if: github.event.pull_request.draft == false
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
@ -21,6 +44,7 @@ jobs:
|
|||
command: check
|
||||
|
||||
test:
|
||||
if: github.event.pull_request.draft == false
|
||||
name: Test Suite
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
@ -42,6 +66,7 @@ jobs:
|
|||
args: tests
|
||||
|
||||
fmt:
|
||||
if: github.event.pull_request.draft == false
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
@ -59,6 +84,7 @@ jobs:
|
|||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
if: github.event.pull_request.draft == false
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
@ -76,3 +102,24 @@ jobs:
|
|||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
# Coverage is disabled until we can fix it
|
||||
# coverage:
|
||||
# name: Coverage
|
||||
# runs-on: ubuntu-latest
|
||||
# container:
|
||||
# image: xd009642/tarpaulin:develop-nightly
|
||||
# options: --security-opt seccomp=unconfined
|
||||
# steps:
|
||||
# - name: Checkout repository
|
||||
# uses: actions/checkout@v2
|
||||
# - name: Generate code coverage
|
||||
# run: |
|
||||
# apt-get update &&\
|
||||
# apt-get install build-essential &&\
|
||||
# apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\
|
||||
# cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml
|
||||
# - name: Upload to codecov.io
|
||||
# uses: codecov/codecov-action@v2
|
||||
# with:
|
||||
# fail_ci_if_error: false
|
||||
|
|
22
.github/workflows/windows.yml
vendored
22
.github/workflows/windows.yml
vendored
|
@ -1,12 +1,32 @@
|
|||
name: windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- packages/**
|
||||
- examples/**
|
||||
- src/**
|
||||
- .github/**
|
||||
- lib.rs
|
||||
- Cargo.toml
|
||||
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- packages/**
|
||||
- examples/**
|
||||
- src/**
|
||||
- .github/**
|
||||
- lib.rs
|
||||
- Cargo.toml
|
||||
|
||||
jobs:
|
||||
test:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: windows-latest
|
||||
name: (${{ matrix.target }}, ${{ matrix.cfg_release_channel }})
|
||||
env:
|
||||
|
@ -50,7 +70,7 @@ jobs:
|
|||
run: |
|
||||
rustc -Vv
|
||||
cargo -V
|
||||
set RUST_BACKTRACE=1
|
||||
set RUST_BACKTRACE=1
|
||||
cargo build --features "desktop, ssr, router"
|
||||
cargo test --features "desktop, ssr, router"
|
||||
shell: cmd
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,3 +8,4 @@ Cargo.lock
|
|||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
tarpaulin-report.html
|
10
.vscode/spellright.dict
vendored
10
.vscode/spellright.dict
vendored
|
@ -69,3 +69,13 @@ Jank
|
|||
noderef
|
||||
reborrow
|
||||
VirtualDoms
|
||||
bootstrapper
|
||||
WebkitGtk
|
||||
laymans
|
||||
iter
|
||||
cloneable
|
||||
fudamental
|
||||
clonable
|
||||
oninput
|
||||
Webview
|
||||
idanarye
|
||||
|
|
30
Cargo.toml
30
Cargo.toml
|
@ -2,19 +2,22 @@
|
|||
name = "dioxus"
|
||||
version = "0.1.8"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
rust-version = "1.56.0"
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "./packages/core", version = "^0.1.9" }
|
||||
dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true }
|
||||
dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true }
|
||||
dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true }
|
||||
fermi = { path = "./packages/fermi", version = "^0.1.0", optional = true }
|
||||
# dioxus-rsx = { path = "./packages/rsx", optional = true }
|
||||
|
||||
dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true }
|
||||
dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", optional = true }
|
||||
|
@ -29,24 +32,20 @@ dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.0.0", o
|
|||
default = ["macro", "hooks", "html"]
|
||||
|
||||
macro = ["dioxus-core-macro"]
|
||||
# macro = ["dioxus-core-macro", "dioxus-rsx"]
|
||||
hooks = ["dioxus-hooks"]
|
||||
html = ["dioxus-html"]
|
||||
ssr = ["dioxus-ssr"]
|
||||
web = ["dioxus-web"]
|
||||
desktop = ["dioxus-desktop"]
|
||||
ayatana = ["dioxus-desktop/ayatana"]
|
||||
router = ["dioxus-router"]
|
||||
|
||||
# "dioxus-router/web"
|
||||
# "dioxus-router/desktop"
|
||||
# desktop = ["dioxus-desktop", "dioxus-router/desktop"]
|
||||
# mobile = ["dioxus-mobile"]
|
||||
# liveview = ["dioxus-liveview"]
|
||||
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"packages/core",
|
||||
"packages/core-macro",
|
||||
"packages/rsx",
|
||||
"packages/html",
|
||||
"packages/hooks",
|
||||
"packages/web",
|
||||
|
@ -54,18 +53,19 @@ members = [
|
|||
"packages/desktop",
|
||||
"packages/mobile",
|
||||
"packages/interpreter",
|
||||
"packages/fermi",
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
futures-util = "0.3.17"
|
||||
futures-util = "0.3.21"
|
||||
log = "0.4.14"
|
||||
num-format = "0.4.0"
|
||||
separator = "0.4.1"
|
||||
serde = { version = "1.0.131", features = ["derive"] }
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
im-rc = "15.0.0"
|
||||
anyhow = "1.0.51"
|
||||
serde_json = "1.0.73"
|
||||
anyhow = "1.0.53"
|
||||
serde_json = "1.0.79"
|
||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||
tokio = { version = "1.14.0", features = ["full"] }
|
||||
reqwest = { version = "0.11.8", features = ["json"] }
|
||||
dioxus = { path = ".", features = ["desktop", "ssr", "router"] }
|
||||
tokio = { version = "1.16.1", features = ["full"] }
|
||||
reqwest = { version = "0.11.9", features = ["json"] }
|
||||
dioxus = { path = ".", features = ["desktop", "ssr", "router", "fermi"] }
|
||||
|
|
25
README.md
25
README.md
|
@ -1,8 +1,5 @@
|
|||
<div align="center">
|
||||
<h1>🌗🚀 Dioxus</h1>
|
||||
<p>
|
||||
<strong>Frontend that scales.</strong>
|
||||
</p>
|
||||
<h1>Dioxus</h1>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
@ -26,9 +23,7 @@
|
|||
<img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg"
|
||||
alt="CI status" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<!--Awesome -->
|
||||
<a href="https://github.com/dioxuslabs/awesome-dioxus">
|
||||
<img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome Page" />
|
||||
|
@ -45,9 +40,11 @@
|
|||
<h3>
|
||||
<a href="https://dioxuslabs.com"> Website </a>
|
||||
<span> | </span>
|
||||
<a href="https://dioxuslabs.com/guide"> Guide </a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/DioxusLabs/example-projects"> Examples </a>
|
||||
<span> | </span>
|
||||
<a href="https://dioxuslabs.com/guide"> Guide (0.1.8) </a>
|
||||
<span> | </span>
|
||||
<a href="https://dioxuslabs.com/nightly/guide"> Guide (Nightly) </a>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
|
@ -101,10 +98,10 @@ cargo run --example EXAMPLE
|
|||
<table style="width:100%" align="center">
|
||||
<tr >
|
||||
<th><a href="https://dioxuslabs.com/guide/">Tutorial</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/web">Web</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/desktop/">Desktop</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/ssr/">SSR</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/mobile/">Mobile</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/platforms/web">Web</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/platforms/desktop/">Desktop</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/platforms/ssr/">SSR</a></th>
|
||||
<th><a href="https://dioxuslabs.com/reference/platforms/mobile/">Mobile</a></th>
|
||||
<th><a href="https://dioxuslabs.com/guide/concepts/managing_state.html">State</a></th>
|
||||
<tr>
|
||||
</table>
|
||||
|
@ -160,9 +157,9 @@ You shouldn't use Dioxus if:
|
|||
## Comparison with other Rust UI frameworks
|
||||
Dioxus primarily emphasizes **developer experience** and **familiarity with React principles**.
|
||||
|
||||
- [Yew](https://github.com/yewstack/yew): prefers the elm pattern instead of React-hooks, no borrowed props, supports SSR (no hydration).
|
||||
- [Yew](https://github.com/yewstack/yew): prefers the elm pattern instead, no borrowed props, supports SSR (no hydration), no direct desktop/mobile support.
|
||||
- [Percy](https://github.com/chinedufn/percy): Supports SSR but with less emphasis on state management and event handling.
|
||||
- [Sycamore](https://github.com/sycamore-rs/sycamore): VDOM-less using fine-grained reactivity, but lacking in ergonomics.
|
||||
- [Sycamore](https://github.com/sycamore-rs/sycamore): VDOM-less using fine-grained reactivity, but no direct support for desktop/mobile.
|
||||
- [Dominator](https://github.com/Pauan/rust-dominator): Signal-based zero-cost alternative, less emphasis on community and docs.
|
||||
- [Azul](https://azul.rs): Fully native HTML/CSS renderer for desktop applications, no support for web/ssr
|
||||
|
||||
|
|
2
codecov.yml
Normal file
2
codecov.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
comment: false
|
||||
fail_ci_if_error: false
|
1
docs/cli/.gitignore
vendored
Normal file
1
docs/cli/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
book
|
6
docs/cli/book.toml
Normal file
6
docs/cli/book.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[book]
|
||||
authors = ["YuKun Liu"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Dioxus Cli"
|
11
docs/cli/src/SUMMARY.md
Normal file
11
docs/cli/src/SUMMARY.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Summary
|
||||
|
||||
- [Introduction](./introduction.md)
|
||||
- [Installation](./installation.md)
|
||||
- [Create a Project](./creating.md)
|
||||
- [Configure Project](./configure.md)
|
||||
- [Commands](./cmd/README.md)
|
||||
- [Build](./cmd/build.md)
|
||||
- [Serve](./cmd/serve.md)
|
||||
- [Clean](./cmd/clean.md)
|
||||
- [Translate](./cmd/translate.md)
|
26
docs/cli/src/cmd/README.md
Normal file
26
docs/cli/src/cmd/README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Commands
|
||||
|
||||
In this chapter we will introduce all `dioxus-cli` commands.
|
||||
|
||||
> you can also use `dioxus --help` to get cli help info.
|
||||
|
||||
```
|
||||
dioxus
|
||||
Build, bundle, & ship your Dioxus app
|
||||
|
||||
USAGE:
|
||||
dioxus [OPTIONS] <SUBCOMMAND>
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Print help information
|
||||
-v Enable verbose logging
|
||||
|
||||
SUBCOMMANDS:
|
||||
build Build the Rust WASM app and all of its assets
|
||||
clean Clean output artifacts
|
||||
config Dioxus config file controls
|
||||
create Init a new project for Dioxus
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
serve Build, watch & serve the Rust WASM app and all of its assets
|
||||
translate Translate some source file into Dioxus code
|
||||
```
|
47
docs/cli/src/cmd/build.md
Normal file
47
docs/cli/src/cmd/build.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Build
|
||||
|
||||
The `dioxus build` command can help you `pack & build` a dioxus project.
|
||||
|
||||
```
|
||||
dioxus-build
|
||||
Build the Rust WASM app and all of its assets
|
||||
|
||||
USAGE:
|
||||
dioxus build [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--example <EXAMPLE> [default: ""]
|
||||
--platform <PLATFORM> [default: "default_platform"]
|
||||
--release [default: false]
|
||||
```
|
||||
|
||||
You can use this command to build project to `out_dir` :
|
||||
|
||||
```
|
||||
dioxus build --release
|
||||
```
|
||||
|
||||
## Target platform
|
||||
|
||||
Use option `platform` choose build target platform:
|
||||
|
||||
```
|
||||
# for desktop project
|
||||
dioxus build --platform desktop
|
||||
```
|
||||
|
||||
`platform` only supports `desktop` & `web`.
|
||||
|
||||
```
|
||||
# for web project
|
||||
dioxus build --platform web
|
||||
```
|
||||
|
||||
## Build Example
|
||||
|
||||
You can use `--example {name}` to build a example code.
|
||||
|
||||
```
|
||||
# build `example/test` code
|
||||
dioxus build --exmaple test
|
||||
```
|
18
docs/cli/src/cmd/clean.md
Normal file
18
docs/cli/src/cmd/clean.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Clean
|
||||
|
||||
`dioxus clean` will call `target clean` and remove `out_dir` directory.
|
||||
|
||||
```
|
||||
dioxus-clean
|
||||
Clean output artifacts
|
||||
|
||||
USAGE:
|
||||
dioxus clean
|
||||
```
|
||||
|
||||
you can use this command to clean all build cache and the `out_dir` content.
|
||||
|
||||
```
|
||||
dioxus clean
|
||||
```
|
||||
|
49
docs/cli/src/cmd/serve.md
Normal file
49
docs/cli/src/cmd/serve.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Serve
|
||||
|
||||
The `dioxus serve` can start a dev server (include hot-reload tool) to run the project.
|
||||
|
||||
```
|
||||
dioxus-serve
|
||||
Build, watch & serve the Rust WASM app and all of its assets
|
||||
|
||||
USAGE:
|
||||
dioxus serve [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--example <EXAMPLE> [default: ""]
|
||||
--platform <PLATFORM> [default: "default_platform"]
|
||||
--release [default: false]
|
||||
```
|
||||
|
||||
You can use this command to build project and start a `dev server` :
|
||||
|
||||
```
|
||||
dioxus serve
|
||||
```
|
||||
|
||||
## Target platform
|
||||
|
||||
Use option `platform` choose build target platform:
|
||||
|
||||
```
|
||||
# for desktop project
|
||||
dioxus serve --platform desktop
|
||||
```
|
||||
|
||||
`platform` only supports `desktop` & `web`.
|
||||
|
||||
`dev-server` only for `web` project.
|
||||
|
||||
```
|
||||
# for web project
|
||||
dioxus serve --platform web
|
||||
```
|
||||
|
||||
## Serve Example
|
||||
|
||||
You can use `--example {name}` to start a example code.
|
||||
|
||||
```
|
||||
# build `example/test` code
|
||||
dioxus serve --exmaple test
|
||||
```
|
57
docs/cli/src/cmd/translate.md
Normal file
57
docs/cli/src/cmd/translate.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Translate
|
||||
|
||||
`dioxus translate` can translate some source file into Dioxus code.
|
||||
|
||||
```
|
||||
dioxus-translate
|
||||
Translate some source file into Dioxus code
|
||||
|
||||
USAGE:
|
||||
dioxus translate [OPTIONS] [OUTPUT]
|
||||
|
||||
ARGS:
|
||||
<OUTPUT> Output file, stdout if not present
|
||||
|
||||
OPTIONS:
|
||||
-c, --component Activate debug mode
|
||||
-f, --file <FILE> Input file
|
||||
```
|
||||
|
||||
## Translate HTML to stdout
|
||||
|
||||
```
|
||||
dioxus transtale --file ./index.html
|
||||
```
|
||||
|
||||
## Output in a file
|
||||
|
||||
```
|
||||
dioxus translate --component --file ./index.html component.rsx
|
||||
```
|
||||
|
||||
set `component` flag will wrap `dioxus rsx` code in a component function.
|
||||
|
||||
## Example
|
||||
|
||||
```html
|
||||
<div>
|
||||
<h1> Hello World </h1>
|
||||
<a href="https://dioxuslabs.com/">Link</a>
|
||||
</div>
|
||||
```
|
||||
|
||||
Translate HTML to Dioxus component code.
|
||||
|
||||
```rust
|
||||
fn component(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 { "Hello World" },
|
||||
a {
|
||||
href: "https://dioxuslabs.com/",
|
||||
"Link"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
147
docs/cli/src/configure.md
Normal file
147
docs/cli/src/configure.md
Normal file
|
@ -0,0 +1,147 @@
|
|||
# Configure Project
|
||||
|
||||
This chapter will introduce `Dioxus.toml` and anatomy the config file.
|
||||
|
||||
## Structure
|
||||
|
||||
We use `toml` to define some info for `dioxus` project.
|
||||
|
||||
### Application
|
||||
|
||||
1. ***name*** - project name & title
|
||||
2. ***default_platform*** - which platform target for this project.
|
||||
```
|
||||
# current support: web, desktop
|
||||
# default: web
|
||||
default_platform = "web"
|
||||
```
|
||||
change this to `desktop`, the `dioxus build & serve` will default build desktop app.
|
||||
3. ***out_dir*** - which directory to put the output file; use `dioxus build & service`, the output will put at this directory, and the `assets` will be also copy to here.
|
||||
```
|
||||
out_dir = "dist"
|
||||
```
|
||||
4. ***asset_dir*** - which direcotry to put your `static, assets` file, cli will automatic copy all file to `out_dir`, so you can put some resource file in there, like `CSS, JS, Image` file.
|
||||
```
|
||||
asset_dir = "public"
|
||||
```
|
||||
|
||||
### Web.App
|
||||
|
||||
Web platform application config:
|
||||
|
||||
1. ***title*** - this value will display on the web page title. like `<title></title>` tag.
|
||||
```
|
||||
# HTML title tag content
|
||||
title = "dioxus app | ⛺"
|
||||
```
|
||||
|
||||
### Web.Watcher
|
||||
|
||||
Web platform `dev-server` watcher config:
|
||||
|
||||
1. ***reload_html*** - a boolean value; when watcher trigger, regenerate `index.html` file.
|
||||
```
|
||||
# when watcher trigger, regenerate the `index.html`
|
||||
reload_html = true
|
||||
```
|
||||
2. ***watch_path*** - which files & directories will be watcher monitoring.
|
||||
```
|
||||
watch_path = ["src", "public"]
|
||||
```
|
||||
|
||||
### Web.Resource
|
||||
|
||||
Include some `CSS Javascript` resources into html file.
|
||||
|
||||
1. ***style*** - include some style(CSS) file into html.
|
||||
```
|
||||
style = [
|
||||
# include from public_dir.
|
||||
"./assets/style.css",
|
||||
# or some asset from online cdn.
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"
|
||||
]
|
||||
```
|
||||
2. ***script*** - include some script(JS) file into html.
|
||||
```
|
||||
style = [
|
||||
# include from public_dir.
|
||||
"./assets/index.js",
|
||||
# or some asset from online cdn.
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"
|
||||
]
|
||||
```
|
||||
|
||||
### Web.Resource.Dev
|
||||
|
||||
Only include resources at `Dev` mode.
|
||||
|
||||
1. ***style*** - include some style(CSS) file into html.
|
||||
```
|
||||
style = [
|
||||
# include from public_dir.
|
||||
"./assets/style.css",
|
||||
# or some asset from online cdn.
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"
|
||||
]
|
||||
```
|
||||
2. ***script*** - include some script(JS) file into html.
|
||||
```
|
||||
style = [
|
||||
# include from public_dir.
|
||||
"./assets/index.js",
|
||||
# or some asset from online cdn.
|
||||
"https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"
|
||||
]
|
||||
```
|
||||
|
||||
## Config example
|
||||
|
||||
```toml
|
||||
[application]
|
||||
|
||||
# App (Project) Name
|
||||
name = "{{project-name}}"
|
||||
|
||||
# Dioxus App Default Platform
|
||||
# desktop, web, mobile, ssr
|
||||
default_platform = "web"
|
||||
|
||||
# `build` & `serve` dist path
|
||||
out_dir = "dist"
|
||||
|
||||
# resource (public) file folder
|
||||
asset_dir = "public"
|
||||
|
||||
[web.app]
|
||||
|
||||
# HTML title tag content
|
||||
title = "dioxus | ⛺"
|
||||
|
||||
[web.watcher]
|
||||
|
||||
# when watcher trigger, regenerate the `index.html`
|
||||
reload_html = true
|
||||
|
||||
# which files or dirs will be watcher monitoring
|
||||
watch_path = ["src", "public"]
|
||||
|
||||
# include `assets` in web platform
|
||||
[web.resource]
|
||||
|
||||
# CSS style file
|
||||
style = []
|
||||
|
||||
# Javascript code file
|
||||
script = []
|
||||
|
||||
[web.resource.dev]
|
||||
|
||||
# serve: [dev-server] only
|
||||
|
||||
# CSS style file
|
||||
style = []
|
||||
|
||||
# Javascript code file
|
||||
script = []
|
||||
```
|
38
docs/cli/src/creating.md
Normal file
38
docs/cli/src/creating.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Create a Project
|
||||
|
||||
Once you have the Dioxus CLI tool installed, you can use it to create dioxus project.
|
||||
|
||||
## Initializing a default project
|
||||
|
||||
The `dioxus create` command will create a new directory containing a project template.
|
||||
```
|
||||
dioxus create hello-dioxus
|
||||
```
|
||||
|
||||
It will clone a default template from github template: [DioxusLabs/dioxus-template](https://github.com/DioxusLabs/dioxus-template)
|
||||
|
||||
> This default template is use for `web` platform application.
|
||||
|
||||
then you can change the current directory in to the project:
|
||||
|
||||
```
|
||||
cd hello-dioxus
|
||||
```
|
||||
|
||||
> Make sure `wasm32 target` is installed before running the Web project.
|
||||
|
||||
now we can create a `dev server` to display the project:
|
||||
|
||||
```
|
||||
dioxus serve
|
||||
```
|
||||
|
||||
by default, the dioxus dev server will running at: [`http://127.0.0.1:8080/`](http://127.0.0.1:8080/)
|
||||
|
||||
## Initalizing from other repository
|
||||
|
||||
you can assign which repository you want to create:
|
||||
|
||||
```
|
||||
dioxus init hello-dioxus --template=gh:dioxuslabs/dioxus-template
|
||||
```
|
24
docs/cli/src/installation.md
Normal file
24
docs/cli/src/installation.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Installation
|
||||
|
||||
There are multiple ways to install the Dioxus CLI tool. Choose any one of the methods below that best suit your needs.
|
||||
|
||||
## Install from latest master version
|
||||
|
||||
We suggest you use `github master` version to install it now.
|
||||
|
||||
We have not yet released the latest version to `crates.io`
|
||||
|
||||
```
|
||||
cargo install --git https://github.com/Dioxuslabs/cli
|
||||
```
|
||||
|
||||
This will automatically download `Dioxus-CLI` source from github master branch,
|
||||
and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).
|
||||
|
||||
## Install from `crates.io` version
|
||||
|
||||
```
|
||||
cargo install dioxus-cli
|
||||
```
|
||||
|
||||
Make sure to add the Cargo bin directory to your `PATH`.
|
21
docs/cli/src/introduction.md
Normal file
21
docs/cli/src/introduction.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Introduction
|
||||
|
||||
📦✨ **Dioxus-Cli** is a tool to help get dioxus projects off the ground.
|
||||
|
||||
![dioxus-logo](https://dioxuslabs.com/guide/images/dioxuslogo_full.png)
|
||||
|
||||
It includes `dev server`, `hot reload` and some `quick command` to help you use dioxus.
|
||||
|
||||
## Features
|
||||
|
||||
- [x] `html` to `rsx` conversion tool
|
||||
- [x] hot reload for `web` platform
|
||||
- [x] create dioxus project from `git` repo
|
||||
- [x] build & pack dioxus project
|
||||
- [ ] autoformat dioxus `rsx` code
|
||||
|
||||
## Contributors
|
||||
|
||||
Contributors to this guide:
|
||||
|
||||
- [mrxiaozhuox](https://github.com/mrxiaozhuox)
|
1
docs/fermi/.gitignore
vendored
Normal file
1
docs/fermi/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
book
|
6
docs/fermi/book.toml
Normal file
6
docs/fermi/book.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[book]
|
||||
authors = ["Jonathan Kelley"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Fermi Guide"
|
3
docs/fermi/src/SUMMARY.md
Normal file
3
docs/fermi/src/SUMMARY.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Summary
|
||||
|
||||
- [Chapter 1](./chapter_1.md)
|
1
docs/fermi/src/chapter_1.md
Normal file
1
docs/fermi/src/chapter_1.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Chapter 1
|
|
@ -5,24 +5,24 @@
|
|||
**Dioxus** is a framework and ecosystem for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get started with Dioxus running on the Web, Desktop, Mobile, and more.
|
||||
|
||||
```rust
|
||||
fn App(cx: Scope) -> Element {
|
||||
let mut count = use_state(&cx, || 0);
|
||||
fn app(cx: Scope) -> Element {
|
||||
let (count, set_count) = use_state(&cx, || 0);
|
||||
|
||||
cx.render(rsx!(
|
||||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
button { onclick: move |_| set_count(count + 1), "Up high!" }
|
||||
button { onclick: move |_| set_count(count - 1), "Down low!" }
|
||||
))
|
||||
};
|
||||
```
|
||||
|
||||
In general, Dioxus and React share many functional similarities. If this guide is lacking in any general concept or an error message is confusing, React's documentation might be more helpful. We are dedicated to providing a *familiar* toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc). If you know React, then you already know Dioxus. If you don't know either, this guide will still help you!
|
||||
|
||||
> This is an introduction book! For advanced topics, check out the [Reference](https://dioxuslabs.com/reference) instead.
|
||||
> This is an introduction book! For advanced topics, check out the [Reference](/reference) instead.
|
||||
|
||||
## Multiplatform
|
||||
|
||||
Dioxus is a *portable* toolkit, meaning the Core implementation can run anywhere with no platform-dependent linking. Unlike many other Rust frontend toolkits, Dioxus is not intrinsically linked to Web-Sys. In fact, every element and event listener can be swapped out at compile time. By default, Dioxus ships with the `html` feature enabled, but this can be disabled depending on your target renderer.
|
||||
Dioxus is a *portable* toolkit, meaning the Core implementation can run anywhere with no platform-dependent linking. Unlike many other Rust frontend toolkits, Dioxus is not intrinsically linked to WebSys. In fact, every element and event listener can be swapped out at compile time. By default, Dioxus ships with the `html` feature enabled, but this can be disabled depending on your target renderer.
|
||||
|
||||
Right now, we have several 1st-party renderers:
|
||||
- WebSys (for WASM)
|
||||
|
@ -37,7 +37,7 @@ The Web is the best-supported target platform for Dioxus. To run on the Web, you
|
|||
|
||||
Because the web is a fairly mature platform, we expect there to be very little API churn for web-based features.
|
||||
|
||||
[Jump to the getting started guide for the web.]()
|
||||
[Jump to the getting started guide for the web.](/reference/platforms/web)
|
||||
|
||||
Examples:
|
||||
- [TodoMVC](https://github.com/DioxusLabs/example-projects/tree/master/todomvc)
|
||||
|
@ -55,7 +55,7 @@ For rendering statically to an `.html` file or from a WebServer, then you'll wan
|
|||
let contents = dioxus::ssr::render_vdom(&dom);
|
||||
```
|
||||
|
||||
[Jump to the getting started guide for SSR.]()
|
||||
[Jump to the getting started guide for SSR.](/reference/platforms/ssr)
|
||||
|
||||
Examples:
|
||||
- [Example DocSite](https://github.com/dioxusLabs/docsite)
|
||||
|
@ -68,13 +68,13 @@ The desktop is a powerful target for Dioxus, but is currently limited in capabil
|
|||
|
||||
Desktop APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart.
|
||||
|
||||
[Jump to the getting started guide for Desktop.]()
|
||||
[Jump to the getting started guide for Desktop.](/reference/platforms/desktop)
|
||||
|
||||
Examples:
|
||||
- [File explorer](https://github.com/dioxusLabs/file-explorer/)
|
||||
- [WiFi scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner)
|
||||
|
||||
[![File ExplorerExample](https://github.com/DioxusLabs/file-explorer-example/raw/master/image.png)](https://github.com/dioxusLabs/file-explorer/)
|
||||
[![File ExplorerExample](https://raw.githubusercontent.com/DioxusLabs/example-projects/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/tree/master/file-explorer)
|
||||
|
||||
### Mobile Support
|
||||
---
|
||||
|
@ -82,7 +82,7 @@ Mobile is currently the least-supported renderer target for Dioxus. Mobile apps
|
|||
|
||||
Mobile support is currently best suited for CRUD-style apps, ideally for internal teams who need to develop quickly but don't care much about animations or native widgets.
|
||||
|
||||
[Jump to the getting started guide for Mobile.]()
|
||||
[Jump to the getting started guide for Mobile.](/reference/platforms/mobile)
|
||||
|
||||
Examples:
|
||||
- [Todo App](https://github.com/DioxusLabs/example-projects/blob/master/ios_demo)
|
||||
|
|
114
docs/guide/src/ROADMAP.md
Normal file
114
docs/guide/src/ROADMAP.md
Normal file
|
@ -0,0 +1,114 @@
|
|||
# Roadmap & Feature-set
|
||||
|
||||
Before we dive into Dioxus, feel free to take a look at our feature set and roadmap to see if what Dioxus can do today works for you.
|
||||
|
||||
If a feature that you need doesn't exist or you want to contribute to projects on the roadmap, feel free to get involved by [joining the discord](https://discord.gg/XgGxMSkvUM).
|
||||
|
||||
Generally, here's the status of each platform:
|
||||
|
||||
- **Web**: Dioxus is a great choice for pure web-apps - especially for CRUD/complex apps. However, it does lack the ecosystem of React, so you might be missing a component library or some useful hook.
|
||||
|
||||
- **SSR**: Dioxus is a great choice for pre-rendering, hydration, and rendering HTML on a web endpoint. Be warned - the VirtualDom is not (currently) `Send + Sync`.
|
||||
|
||||
- **Desktop**: You can build very competent single-window desktop apps right now. However, multi-window apps require support from Dioxus core and are not ready.
|
||||
|
||||
- **Mobile**: Mobile support is very young. You'll be figuring things out as you go and there are not many support crates for peripherals.
|
||||
|
||||
- **LiveView**: LiveView support is very young. You'll be figuring things out as you go. Thankfully, none of it is too hard and any work can be upstreamed into Dioxus.
|
||||
|
||||
## Features
|
||||
---
|
||||
|
||||
| Feature | Status | Description |
|
||||
| ------------------------- | ------ | -------------------------------------------------------------------- |
|
||||
| Conditional Rendering | ✅ | if/then to hide/show component |
|
||||
| Map, Iterator | ✅ | map/filter/reduce to produce rsx! |
|
||||
| Keyed Components | ✅ | advanced diffing with keys |
|
||||
| Web | ✅ | renderer for web browser |
|
||||
| Desktop (webview) | ✅ | renderer for desktop |
|
||||
| Shared State (Context) | ✅ | share state through the tree |
|
||||
| Hooks | ✅ | memory cells in components |
|
||||
| SSR | ✅ | render directly to string |
|
||||
| Component Children | ✅ | cx.children() as a list of nodes |
|
||||
| Headless components | ✅ | components that don't return real elements |
|
||||
| Fragments | ✅ | multiple elements without a real root |
|
||||
| Manual Props | ✅ | Manually pass in props with spread syntax |
|
||||
| Controlled Inputs | ✅ | stateful wrappers around inputs |
|
||||
| CSS/Inline Styles | ✅ | syntax for inline styles/attribute groups |
|
||||
| Custom elements | ✅ | Define new element primitives |
|
||||
| Suspense | ✅ | schedule future render from future/promise |
|
||||
| Integrated error handling | ✅ | Gracefully handle errors with ? syntax |
|
||||
| NodeRef | ✅ | gain direct access to nodes |
|
||||
| Re-hydration | ✅ | Pre-render to HTML to speed up first contentful paint |
|
||||
| Jank-Free Rendering | ✅ | Large diffs are segmented across frames for silky-smooth transitions |
|
||||
| Effects | ✅ | Run effects after a component has been committed to render |
|
||||
| Portals | 🛠 | Render nodes outside of the traditional tree structure |
|
||||
| Cooperative Scheduling | 🛠 | Prioritize important events over non-important events |
|
||||
| Server Components | 🛠 | Hybrid components for SPA and Server |
|
||||
| Bundle Splitting | 👀 | Efficiently and asynchronously load the app |
|
||||
| Lazy Components | 👀 | Dynamically load the new components as the page is loaded |
|
||||
| 1st class global state | ✅ | redux/recoil/mobx on top of context |
|
||||
| Runs natively | ✅ | runs as a portable binary w/o a runtime (Node) |
|
||||
| Subtree Memoization | ✅ | skip diffing static element subtrees |
|
||||
| High-efficiency templates | 🛠 | rsx! calls are translated to templates on the DOM's side |
|
||||
| Compile-time correct | ✅ | Throw errors on invalid template layouts |
|
||||
| Heuristic Engine | ✅ | track component memory usage to minimize future allocations |
|
||||
| Fine-grained reactivity | 👀 | Skip diffing for fine-grain updates |
|
||||
|
||||
- ✅ = implemented and working
|
||||
- 🛠 = actively being worked on
|
||||
- 👀 = not yet implemented or being worked on
|
||||
- ❓ = not sure if will or can implement
|
||||
|
||||
|
||||
## Roadmap
|
||||
---
|
||||
|
||||
|
||||
Core:
|
||||
- [x] Release of Dioxus Core
|
||||
- [x] Upgrade documentation to include more theory and be more comprehensive
|
||||
- [ ] Support for HTML-side templates for lightning-fast dom manipulation
|
||||
- [ ] Support for multiple renderers for same virtualdom (subtrees)
|
||||
- [ ] Support for ThreadSafe (Send + Sync)
|
||||
- [ ] Support for Portals
|
||||
|
||||
SSR
|
||||
- [x] SSR Support + Hydration
|
||||
- [ ] Integrated suspense support for SSR
|
||||
|
||||
Desktop
|
||||
- [ ] Declarative window management
|
||||
- [ ] Templates for building/bundling
|
||||
- [ ] Fully native renderer
|
||||
- [ ] Access to Canvas/WebGL context natively
|
||||
|
||||
Mobile
|
||||
- [ ] Mobile standard library
|
||||
- [ ] GPS
|
||||
- [ ] Camera
|
||||
- [ ] filesystem
|
||||
- [ ] Biometrics
|
||||
- [ ] WiFi
|
||||
- [ ] Bluetooth
|
||||
- [ ] Notifications
|
||||
- [ ] Clipboard
|
||||
- [ ]
|
||||
|
||||
Bundling (CLI)
|
||||
- [x] translation from HTML into RSX
|
||||
- [ ] dev server
|
||||
- [ ] live reload
|
||||
- [ ] translation from JSX into RSX
|
||||
- [ ] hot module replacement
|
||||
- [ ] code splitting
|
||||
- [ ] asset macros
|
||||
- [ ] css pipeline
|
||||
- [ ] image pipeline
|
||||
|
||||
Essential hooks
|
||||
- [ ] Router
|
||||
- [ ] Global state management
|
||||
- [ ] Resize observer
|
||||
|
||||
|
|
@ -1,19 +1,23 @@
|
|||
# Summary
|
||||
|
||||
- [Introduction](README.md)
|
||||
- [Roadmap](ROADMAP.md)
|
||||
- [Getting Set Up](setup.md)
|
||||
- [Hello, World!](hello_world.md)
|
||||
- [Describing the UI](elements/index.md)
|
||||
- [Intro to Elements](elements/vnodes.md)
|
||||
- [Intro to Components](elements/components.md)
|
||||
- [The Props Macro](elements/propsmacro.md)
|
||||
- [Reusing, Importing, and Exporting Components](elements/exporting_components.md)
|
||||
- [Passing children and attributes](elements/component_children.md)
|
||||
- [Conditional Rendering](elements/conditional_rendering.md)
|
||||
- [Lists](elements/lists.md)
|
||||
- [Special Attributes](elements/special_attributes.md)
|
||||
- [Components](components/index.md)
|
||||
- [Properties](components/propsmacro.md)
|
||||
- [Reusing, Importing, and Exporting](components/exporting_components.md)
|
||||
- [Children and Attributes](components/component_children.md)
|
||||
- [How Data Flows](components/composing.md)
|
||||
- [Adding Interactivity](interactivity/index.md)
|
||||
- [Hooks and Internal State](interactivity/hooks.md)
|
||||
- [Event handlers](interactivity/event_handlers.md)
|
||||
- [UseState and UseRef](interactivity/importanthooks.md)
|
||||
- [Event Listeners](interactivity/event_handlers.md)
|
||||
- [User Input and Controlled Components](interactivity/user_input.md)
|
||||
- [Lifecycle, updates, and effects](interactivity/lifecycles.md)
|
||||
- [Managing State](state/index.md)
|
||||
|
@ -41,3 +45,4 @@
|
|||
|
||||
<!-- - [Suspense](concepts/suspense.md) -->
|
||||
<!-- - [Async Callbacks](concepts/asynccallbacks.md) -->
|
||||
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
# Fetching
|
||||
|
||||
|
||||
This section is currently under construction! 🏗
|
||||
|
|
|
@ -17,3 +17,6 @@ Writing apps that deal with Send/Sync can be frustrating at times. Under the hoo
|
|||
|
||||
|
||||
All async code in your app is polled on a `LocalSet`, so any async code we w
|
||||
|
||||
|
||||
> This section is currently under construction! 🏗
|
||||
|
|
|
@ -30,7 +30,7 @@ struct ClickableProps<'a> {
|
|||
title: &'a str
|
||||
}
|
||||
|
||||
fn Clickable(cx: Scope<ClickableProps>) -> Element {
|
||||
fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
|
||||
cx.render(rsx!(
|
||||
a {
|
||||
href: "{cx.props.href}"
|
||||
|
@ -64,7 +64,7 @@ struct ClickableProps<'a> {
|
|||
body: Element<'a>
|
||||
}
|
||||
|
||||
fn Clickable(cx: Scope<ClickableProps>) -> Element {
|
||||
fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
|
||||
cx.render(rsx!(
|
||||
a {
|
||||
href: "{cx.props.href}",
|
||||
|
@ -98,7 +98,7 @@ struct ClickableProps<'a> {
|
|||
children: Element<'a>
|
||||
}
|
||||
|
||||
fn Clickable(cx: Scope<ClickableProps>) -> Element {
|
||||
fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
|
||||
cx.render(rsx!(
|
||||
a {
|
||||
href: "{cx.props.href}",
|
||||
|
@ -125,7 +125,7 @@ While technically allowed, it's an antipattern to pass children more than once i
|
|||
However, because the `Element` is transparently a `VNode`, we can actually match on it to extract the nodes themselves, in case we are expecting a specific format:
|
||||
|
||||
```rust
|
||||
fn clickable(cx: Scope<ClickableProps>) -> Element {
|
||||
fn clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
|
||||
match cx.props.children {
|
||||
Some(VNode::Text(text)) => {
|
||||
// ...
|
||||
|
@ -137,7 +137,7 @@ fn clickable(cx: Scope<ClickableProps>) -> Element {
|
|||
}
|
||||
```
|
||||
|
||||
## Passing attributes
|
||||
<!-- ## Passing attributes
|
||||
|
||||
In the cases where you need to pass arbitrary element properties into a component - say to add more functionality to the `<a>` tag, Dioxus will accept any quoted fields. This is similar to adding arbitrary fields to regular elements using quotes.
|
||||
|
||||
|
@ -160,7 +160,7 @@ struct ClickableProps<'a> {
|
|||
attributes: Attributes<'a>
|
||||
}
|
||||
|
||||
fn clickable(cx: Scope<ClickableProps>) -> Element {
|
||||
fn clickable(cx: Scope<ClickableProps<'a>>) -> Element {
|
||||
cx.render(rsx!(
|
||||
a {
|
||||
..cx.props.attributes,
|
||||
|
@ -171,7 +171,7 @@ fn clickable(cx: Scope<ClickableProps>) -> Element {
|
|||
```
|
||||
|
||||
The quoted escapes are a great way to make your components more flexible.
|
||||
|
||||
-->
|
||||
|
||||
## Passing handlers
|
||||
|
||||
|
@ -184,7 +184,7 @@ struct ClickableProps<'a> {
|
|||
onclick: EventHandler<'a, MouseEvent>
|
||||
}
|
||||
|
||||
fn clickable(cx: Scope<ClickableProps>) -> Element {
|
||||
fn clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
|
||||
cx.render(rsx!(
|
||||
a {
|
||||
onclick: move |evt| cx.props.onclick.call(evt)
|
||||
|
@ -215,5 +215,3 @@ In this chapter, we learned:
|
|||
- How to convert `listeners` into `EventHandlers` for components
|
||||
- How to extend any node with custom attributes and children
|
||||
|
||||
Next chapter, we'll talk about conditionally rendering parts of your user interface.
|
||||
|
BIN
docs/guide/src/components/component_example_title.png
Normal file
BIN
docs/guide/src/components/component_example_title.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
BIN
docs/guide/src/components/component_example_votes.png
Normal file
BIN
docs/guide/src/components/component_example_votes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
243
docs/guide/src/components/composing.md
Normal file
243
docs/guide/src/components/composing.md
Normal file
|
@ -0,0 +1,243 @@
|
|||
# Thinking in React
|
||||
|
||||
We've finally reached the point in our tutorial where we can talk about the theory of Reactive interfaces. We've talked about defining a declarative view, but not about the aspects that make our code *reactive*.
|
||||
|
||||
Understanding the theory of reactive programming is essential to making sense of Dioxus and writing effective, performant UIs.
|
||||
|
||||
In this section, we'll talk about:
|
||||
|
||||
- One-way data flow
|
||||
- Modifying data
|
||||
- Forcing renders
|
||||
- How renders propagate
|
||||
|
||||
This section is a bit long, but worth the read. We recommend coffee, tea, and/or snacks.
|
||||
|
||||
## Reactive Programming
|
||||
|
||||
Dioxus is one the very few Rust libraries that provide a "Reactive Programming Model". The term "Reactive programming" is a classification of programming paradigm - much like functional or imperative programming. This is a very important distinction since it affects how we *think* about our code.
|
||||
|
||||
Reactive programming is a programming model concerned with deriving computations from asynchronous data flow. Most reactive programs are comprised of datasources, intermediate computations, and a final result.
|
||||
|
||||
We consider the rendered GUI to be the final result of our Dioxus apps. The datasources for our apps include local and global state.
|
||||
|
||||
For example, the model presented in the figure below is comprised of two data sources: time and a constant. These values are passed through our computation graph to achieve a final result: `g`.
|
||||
|
||||
![Reactive Model](https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/Reactive_programming_glitches.svg/440px-Reactive_programming_glitches.svg.png)
|
||||
|
||||
Whenever our `seconds` variable changes, we will then reevaluate the computation for `t`. Because `g` relies on `t`, we will also reevaluate its computation too. Notice that we would've reevaluated the computation for `g` even if `t` didn't change because `seconds` is used to calculate `g`.
|
||||
|
||||
However, if we somehow changed our constant from `1` to `2`, then we need to reevaluate `t`. If, for whatever reason, this change did not affect the result of `t`, then we wouldn't try to reevaluate `g`.
|
||||
|
||||
In Reactive Programming, we don't think about whether or not we should reevaluate `t` or `g`; instead, we simply provide functions of computation and let the framework figure out the rest for us.
|
||||
|
||||
In Rust, our reactive app would look something like:
|
||||
|
||||
```rust
|
||||
fn compute_g(t: i32, seconds: i32) -> bool {
|
||||
t > seconds
|
||||
}
|
||||
|
||||
fn compute_t(constant: i32, seconds: i32) -> i32 {
|
||||
constant + seconds
|
||||
}
|
||||
|
||||
fn compute_graph(constant: i32, seconds: i32) -> bool {
|
||||
let t = compute_t(constant, seconds);
|
||||
let g = compute_g(t, seconds);
|
||||
g
|
||||
}
|
||||
```
|
||||
|
||||
## How is Dioxus Reactive?
|
||||
|
||||
The Dioxus VirtualDom provides us a framework for reactive programming. When we build apps with dioxus, we need to provide our own datasources. This can be either initial props or some values fetched from the network. We then pass this data through our app into components through properties.
|
||||
|
||||
If we represented the reactive graph presented above in Dioxus, it would look very similar:
|
||||
|
||||
```rust
|
||||
// Declare a component that holds our datasources and calculates `g`
|
||||
fn RenderGraph(cx: Scope) -> Element {
|
||||
let seconds = use_datasource(SECONDS);
|
||||
let constant = use_state(&cx, || 1);
|
||||
|
||||
cx.render(rsx!(
|
||||
RenderG { seconds: seconds }
|
||||
RenderT { seconds: seconds, constant: constant }
|
||||
))
|
||||
}
|
||||
|
||||
// "calculate" g by rendering `t` and `seconds`
|
||||
#[inline_props]
|
||||
fn RenderG(cx: Scope, seconds: i32) -> Element {
|
||||
cx.render(rsx!{ "There are {seconds} seconds remaining..." })
|
||||
}
|
||||
|
||||
// calculate and render `t` in its own component
|
||||
#[inline_props]
|
||||
fn RenderT(cx: Scope, seconds: i32, constant: i32) -> Element {
|
||||
let res = seconds + constant;
|
||||
cx.render(rsx!{ "{res}" })
|
||||
}
|
||||
```
|
||||
|
||||
With this app, we've defined three components. Our top-level component provides our datasources (the hooks), computation nodes (child components), and a final value (what's "rendered").
|
||||
|
||||
Now, whenever the `constant` changes, our `RenderT` component will be re-rendered. However, if `seconds` doesn't change, then we don't need to re-render `RenderG` because the input is the same. If `seconds` *does* change, then both RenderG and RenderT will be reevaluated.
|
||||
|
||||
Dioxus is "Reactive" because it provides this framework for us. All we need to do is write our own tiny units of computation and Dioxus figures out which components need to be reevaluated automatically.
|
||||
|
||||
These extra checks and algorithms add some overhead, which is why you see projects like [Sycamore](http://sycamore-rs.netlify.app) and [SolidJS](http://solidjs.com) eliminating them altogether. Dioxus is *really* fast, so we're willing to exchange the added overhead for improved developer experience.
|
||||
|
||||
## How do we update values in our dataflow graph?
|
||||
|
||||
Dioxus will automatically figure out how to regenerate parts of our app when datasources change. But how exactly can we update our data sources?
|
||||
|
||||
In Dioxus there are two datasources:
|
||||
|
||||
1. Local state in `use_hook` and all other hooks
|
||||
2. Global state through `provide_context`.
|
||||
|
||||
Technically, the root props of the VirtualDom are a third datasource, but since we cannot modify them, they are not worth talking about.
|
||||
|
||||
### Local State
|
||||
|
||||
For local state in hooks, Dioxus gives us the `use_hook` method which returns an `&mut T` without any requirements. This means raw hook values are not tracked by Dioxus. In fact, we could write a component that modifies a hook value directly:
|
||||
|
||||
```rust
|
||||
fn app(cx: Scope) -> Element {
|
||||
let mut count = cx.use_hook(|_| 0);
|
||||
cx.render(rsx!{
|
||||
button {
|
||||
onclick: move |_| *count += 1,
|
||||
"Count: {count}"
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
However, when this value is written to, the component does not know to be reevaluated. We must explicitly tell Dioxus that this component is "dirty" and needs to be re-rendered. This is done through the `cx.needs_update` method:
|
||||
|
||||
```rust
|
||||
button {
|
||||
onclick: move |_| {
|
||||
*count += 1;
|
||||
cx.needs_update();
|
||||
},
|
||||
"Count: {count}"
|
||||
}
|
||||
```
|
||||
|
||||
Now, whenever we click the button, the value will change and the component will be re-rendered.
|
||||
|
||||
> Re-rendering is when Dioxus calls your function component *again*. Component functions will be called over and over throughout their lifetime, so they should be mostly side-effect free.
|
||||
|
||||
### Understand this!
|
||||
|
||||
Your component functions will be called ("rendered" in our lingo) for as long as the component is present in the tree.
|
||||
|
||||
A single component will be called multiple times, modifying its own internal state or rendering new nodes with new values from its properties.
|
||||
|
||||
### App-Global State
|
||||
|
||||
With the `provide_context` and `consume_context` methods on `Scope`, we can share values to descendants without having to pass values through component props. This has the side-effect of making our datasources less obvious from a high-level perspective, but it makes our components more modular within the same codebase.
|
||||
|
||||
To make app-global state easier to reason about, Dioxus makes all values provided through `provide_context` immutable. This means any library built on top of `provide_context` needs to use interior mutability to modify shared global state.
|
||||
|
||||
In these cases, App-Global state needs to manually track which components need to be re-generated.
|
||||
|
||||
To regenerate *any* component in your app, you can get a handle to the Dioxus' internal scheduler through `schedule_update_any`:
|
||||
|
||||
```rust
|
||||
let force_render = cx.schedule_update_any();
|
||||
|
||||
// force a render of the root component
|
||||
force_render(ScopeId(0));
|
||||
```
|
||||
|
||||
## What does it mean for a component to "re-render"?
|
||||
|
||||
In our guides, we frequently use the phrase "re-render" to describe updates to our app. You'll often hear this paired with "preventing unnecessary re-renders." But what exactly does this mean?
|
||||
|
||||
When we call `dioxus::desktop::launch`, Dioxus will create a new `Scope` object and call the component we gave it. Our `rsx!` calls will create new nodes which we return back to the VirtualDom. Dioxus will then look through these nodes for child components, call their functions, and so on until every component has been "rendered." We consider these nodes "rendered" because they were created because of our explicit actions.
|
||||
|
||||
The tree of UI that dioxus creates will roughly look like the tree of components presented earlier:
|
||||
|
||||
![Tree of UI](../images/component_tree.png)
|
||||
|
||||
But what happens when we call `needs_update` after modifying some important state? Well, if Dioxus called our component's function again, then we would produce new, different nodes. In fact, this is exactly what Dioxus does!
|
||||
|
||||
At this point, we have some old nodes and some new nodes. Again, we call this "rendering" because Dioxus had to create new nodes because of our explicit actions. Any time new nodes get created, our VirtualDom is being "rendered."
|
||||
|
||||
These nodes are stored in an extremely efficient memory allocator called a "bump arena." For example, a div with a handler and attribute would be stored in memory in two locations: the "old" tree and the "new" tree.
|
||||
|
||||
![Bump Arenas](../images/oldnew.png)
|
||||
|
||||
From here, Dioxus computes the difference between these trees and updates the Real DOM to make it look like the new version of what we've declared.
|
||||
|
||||
![Diffing](../images/diffing.png)
|
||||
|
||||
## Suppressing Renders
|
||||
|
||||
So, we know how to make Dioxus render, but how do we *stop* it? What if we *know* that our state didn't change and we shouldn't render and diff new nodes because they'll be exactly the same as the last time?
|
||||
|
||||
In these cases, you want to reach for *memoization*. In Dioxus, memoization involves preventing a component from rendering again if its props didn't change since the last time it attempted to render.
|
||||
|
||||
Visually, you can tell that a component will only re-render if the new value is sufficiently different than the old one.
|
||||
|
||||
| props.val | re-render |
|
||||
| --------- | --------- |
|
||||
| 10 | true |
|
||||
| 20 | true |
|
||||
| 20 | false |
|
||||
| 20 | false |
|
||||
| 10 | true |
|
||||
| 30 | false |
|
||||
|
||||
This is why when you `derive(Props)`, you must also implement the `PartialEq` trait. To override the memoization strategy for a component, you can simply implement your own PartialEq.
|
||||
|
||||
```rust
|
||||
struct CustomProps {
|
||||
val: i32,
|
||||
}
|
||||
|
||||
impl PartialEq for CustomProps {
|
||||
fn partial_eq(&self, other: &Self) -> bool {
|
||||
// we don't render components that have a val less than 5
|
||||
if other.val > 5 && self.val > 5{
|
||||
self.val == other.val
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
However, for components that borrow data, it doesn't make sense to implement PartialEq since the actual references in memory might be different.
|
||||
|
||||
You can technically override this behavior by implementing the `Props` trait manually, though it's unsafe and easy to mess up:
|
||||
|
||||
```rust
|
||||
impl Properties for CustomProps {
|
||||
fn memoize(&self, other &Self) -> bool {
|
||||
self != other
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
TLDR:
|
||||
- Dioxus checks if props changed between renders
|
||||
- If props changed according to PartialEq, Dioxus re-renders the component
|
||||
- Props that have a lifetime (ie `<'a>`) will always be re-rendered
|
||||
|
||||
## Wrapping Up
|
||||
|
||||
Wow, that was a lot of material!
|
||||
|
||||
Let's see if we can recap what was presented:
|
||||
|
||||
- Reactive programming calculates a final value from datasources and computation
|
||||
- Dioxus is "reactive" since it figures out which computations to check
|
||||
- `schedule_update` must be called to mark a component as dirty
|
||||
- dirty components will be re-rendered (called multiple times) to produce a new UI
|
||||
- Renders can be suppressed with memoization
|
||||
|
||||
This theory is crucial to understand how to compose components and how to control renders in your app.
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
# Reusing, Importing, and Exporting Components
|
||||
|
||||
As your application grows in size, you'll want to start breaking your UI into components and, eventually, different files. This is a great idea to encapsulate functionality of your UI and scale your team.
|
||||
|
@ -66,6 +65,16 @@ fn ActionCard(Scope<ActionCardProps>) -> Element {}
|
|||
|
||||
We should also create a `mod.rs` file in the `post` folder so we can use it from our `main.rs`. Our `Post` component and its props will go into this file.
|
||||
|
||||
```rust
|
||||
// src/post/mod.rs
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct PostProps {}
|
||||
fn Post(Scope<PostProps>) -> Element {}
|
||||
```
|
||||
|
||||
```shell
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
|
@ -78,8 +87,6 @@ We should also create a `mod.rs` file in the `post` folder so we can use it from
|
|||
└── mod.rs
|
||||
```
|
||||
|
||||
|
||||
|
||||
In our `main.rs`, we'll want to declare the `post` module so we can access our `Post` component.
|
||||
|
||||
```rust
|
||||
|
@ -164,7 +171,6 @@ pub fn Post(Scope<PostProps>) -> Element {
|
|||
}
|
||||
```
|
||||
|
||||
|
||||
Ultimately, including and exporting components is governed by Rust's module system. [The Rust book is a great resource to learn about these concepts in greater detail.](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html)
|
||||
|
||||
## Final structure:
|
||||
|
@ -284,14 +290,3 @@ use dioxus::prelude::*;
|
|||
pub struct ActionCardProps {}
|
||||
pub fn ActionCard(Scope<ActionCardProps>) -> Element {}
|
||||
```
|
||||
|
||||
## Moving forward
|
||||
|
||||
Next chapter, we'll start to add use code to hide and show Elements with conditional rendering.
|
||||
|
||||
For more reading on components:
|
||||
|
||||
- [Components in depth]()
|
||||
- [Lifecycles]()
|
||||
- [The Context object]()
|
||||
- [Optional Prop fields]()
|
42
docs/guide/src/components/index.md
Normal file
42
docs/guide/src/components/index.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Introduction to Components
|
||||
|
||||
In the previous chapter, we learned about Elements and how they can be composed to create a basic user interface. Now, we'll learn how to group Elements together to form Components. We'll cover:
|
||||
|
||||
- What makes a Component
|
||||
- How to model a component and its properties in Dioxus
|
||||
- How to "think declaratively"
|
||||
|
||||
## What is a component?
|
||||
|
||||
In short, a component is a special function that takes input properties and outputs an Element. Much like a function encapsulates some specific computation task, a Component encapsulates some specific rendering task – typically, rendering an isolated part of the user interface.
|
||||
|
||||
### Real-world example
|
||||
|
||||
Let's use a Reddit post as an example:
|
||||
|
||||
![Reddit Post](../images/reddit_post.png)
|
||||
|
||||
If we look at the layout of the component, we notice quite a few buttons and pieces of functionality:
|
||||
|
||||
- Upvote/Downvote
|
||||
- View comments
|
||||
- Share
|
||||
- Save
|
||||
- Hide
|
||||
- Give award
|
||||
- Report
|
||||
- Crosspost
|
||||
- Filter by site
|
||||
- View article
|
||||
- Visit user
|
||||
|
||||
If we included all this functionality in one `rsx!` call it would be huge! Instead, let's break the post down into Components:
|
||||
|
||||
![Post as Component](../images/reddit_post_components.png)
|
||||
|
||||
- **VoteButton**: Upvote/Downvote
|
||||
- **TitleCard**: Title, Filter-By-Url
|
||||
- **MetaCard**: Original Poster, Time Submitted
|
||||
- **ActionCard**: View comments, Share, Save, Hide, Give award, Report, Crosspost
|
||||
|
||||
In this chapter, we'll learn how to define these components.
|
200
docs/guide/src/components/propsmacro.md
Normal file
200
docs/guide/src/components/propsmacro.md
Normal file
|
@ -0,0 +1,200 @@
|
|||
# Component Properties
|
||||
Dioxus components are functions that accept Props as input and output an Element. In fact, the `App` function you saw in the previous chapter was a component with no Props! Most components, however, will need to take some Props to render something useful – so, in this section, we'll learn about props:
|
||||
|
||||
- Deriving the Props trait
|
||||
- Memoization through PartialEq
|
||||
- Optional fields on props
|
||||
- The inline_props macro
|
||||
|
||||
## Props
|
||||
The input of your Component must be passed in a single struct, which must implement the `Props` trait. We can derive this trait automatically with `#[derive(Props)]`.
|
||||
|
||||
> Dioxus `Props` is very similar to [@idanarye](https://github.com/idanarye)'s [TypedBuilder crate](https://github.com/idanarye/rust-typed-builder) and supports many of the same parameters.
|
||||
|
||||
There are 2 flavors of Props: owned and borrowed.
|
||||
|
||||
- All Owned Props must implement `PartialEq`
|
||||
- Borrowed props [borrow](https://doc.rust-lang.org/beta/rust-by-example/scope/borrow.html) values from the parent Component
|
||||
|
||||
### Owned Props
|
||||
|
||||
Owned Props are very simple – they don't borrow anything. Example:
|
||||
```rust
|
||||
// Remember: owned props must implement PartialEq!
|
||||
#[derive(PartialEq, Props)]
|
||||
struct VoteButtonProps {
|
||||
score: i32
|
||||
}
|
||||
|
||||
fn VoteButton(cx: Scope<VoteButtonProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
div {
|
||||
div { "+" }
|
||||
div { "{cx.props.score}"}
|
||||
div { "-" }
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Now, we can use the VoteButton Component like we would use a regular HTML element:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! (
|
||||
VoteButton { score: 42 }
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
And we can see that the Component indeed gets rendered:
|
||||
|
||||
![Screenshot of running app. Text: "+ \ 42 \ -"](component_example_votes.png)
|
||||
|
||||
> The simplest Owned Props you can have is `()` - or no value at all. This is what the `App` Component takes as props. `Scope` accepts a generic for the Props which defaults to `()`.
|
||||
>
|
||||
> ```rust
|
||||
>// this scope
|
||||
>Scope<()>
|
||||
>
|
||||
>// is the same as this scope
|
||||
>Scope
|
||||
>```
|
||||
|
||||
### Borrowed Props
|
||||
|
||||
Owning props works well if your props are easy to copy around - like a single number. But what if we need to pass a larger data type, like a String from an `App` Component to a `TitleCard` subcomponent? A naive solution might be to [`.clone()`](https://doc.rust-lang.org/std/clone/trait.Clone.html) the String, creating a copy of it for the subcomponent – but this would be inefficient, especially for larger Strings.
|
||||
|
||||
Rust allows for something more efficient – borrowing the String as a `&str`. Instead of creating a copy, this will give us a reference to the original String – this is what Borrowed Props are for!
|
||||
|
||||
However, if we create a reference a String, Rust will require us to show that the String will not go away while we're using the reference. Otherwise, if we referenced something that doesn't exist, Bad Things could happen. To prevent this, Rust asks us to define a lifetime for the reference:
|
||||
|
||||
```rust
|
||||
#[derive(Props)]
|
||||
struct TitleCardProps<'a> {
|
||||
title: &'a str,
|
||||
}
|
||||
|
||||
fn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{cx.props.title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
This lifetime `'a` tells the compiler that as long as `title` exists, the String it was created from must also exist. Dioxus will happily accept such a component – we can now render it alongside our VoteButton!
|
||||
|
||||
```rust
|
||||
fn App(cx: Scope) -> Element {
|
||||
// For the sake of an example, we create the &str here.
|
||||
// But you might as well borrow it from an owned String type.
|
||||
let hello = "Hello Dioxus!";
|
||||
|
||||
cx.render(rsx! (
|
||||
VoteButton { score: 42 },
|
||||
TitleCard { title: hello }
|
||||
))
|
||||
}
|
||||
```
|
||||
![New screenshot of running app, now including a "Hello Dioxus!" heading.](component_example_title.png)
|
||||
|
||||
## Memoization
|
||||
|
||||
Dioxus uses Memoization for a more efficient user interface. Memoization is the process in which we check if a component actually needs to be re-rendered when its props change. If a component's properties change but they wouldn't affect the output, then we don't need to re-render the component, saving time!
|
||||
|
||||
For example, let's say we have a component that has two children:
|
||||
|
||||
```rust
|
||||
fn Demo(cx: Scope) -> Element {
|
||||
// don't worry about these 2, we'll cover them later
|
||||
let name = use_state(&cx, || String::from("bob"));
|
||||
let age = use_state(&cx, || 21);
|
||||
|
||||
cx.render(rsx!{
|
||||
Name { name: name }
|
||||
Age { age: age }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
If `name` changes but `age` does not, then there is no reason to re-render our `Age` component since the contents of its props did not meaningfully change.
|
||||
|
||||
Dioxus memoizes owned components. It uses `PartialEq` to determine if a component needs rerendering, or if it has stayed the same. This is why you must derive PartialEq!
|
||||
|
||||
> This means you can always rely on props with `PartialEq` or no props at all to act as barriers in your app. This can be extremely useful when building larger apps where properties frequently change. By moving our state into a global state management solution, we can achieve precise, surgical re-renders, improving the performance of our app.
|
||||
|
||||
Borrowed Props cannot be safely memoized. However, this is not a problem – Dioxus relies on the memoization of their parents to determine if they need to be rerendered.
|
||||
|
||||
## Optional Props
|
||||
|
||||
You can easily create optional fields by attaching the `optional` modifier to a field:
|
||||
|
||||
```rust
|
||||
#[derive(Props, PartialEq)]
|
||||
struct MyProps {
|
||||
name: String,
|
||||
|
||||
#[props(optional)]
|
||||
description: Option<String>
|
||||
}
|
||||
|
||||
fn Demo(cx: MyProps) -> Element {
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
Then, we can completely omit the description field when calling the component:
|
||||
|
||||
```rust
|
||||
rsx!{
|
||||
Demo {
|
||||
name: "Thing".to_string(),
|
||||
// description is omitted
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `optional` modifier is a combination of two separate modifiers: `default` and `strip_option`. The full list of modifiers includes:
|
||||
|
||||
- `default` - automatically add the field using its `Default` implementation
|
||||
- `strip_option` - automatically wrap values at the call site in `Some`
|
||||
- `optional` - alias for `default` and `strip_option`
|
||||
- `into` - automatically call `into` on the value at the callsite
|
||||
|
||||
For more information on how tags work, check out the [TypedBuilder](https://github.com/idanarye/rust-typed-builder) crate. However, all attributes for props in Dioxus are flattened (no need for `setter` syntax) and the `optional` field is new.
|
||||
|
||||
## The `inline_props` macro
|
||||
|
||||
So far, every Component function we've seen had a corresponding ComponentProps struct to pass in props. This was quite verbose... Wouldn't it be nice to have props as simple function arguments? Then we wouldn't need to define a Props struct, and instead of typing `cx.props.whatever`, we could just use `whatever` directly!
|
||||
|
||||
`inline_props` allows you to do just that. Instead of typing the "full" version:
|
||||
|
||||
```rust
|
||||
#[derive(Props, PartialEq)]
|
||||
struct TitleCardProps {
|
||||
title: String,
|
||||
}
|
||||
|
||||
fn TitleCard(cx: Scope<TitleCardProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{cx.props.title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
...you can define a function that accepts props as arguments. Then, just annotate it with `#[inline_props]`, and the macro will turn it into a regular Component for you:
|
||||
|
||||
```rust
|
||||
#[inline_props]
|
||||
fn TitleCard(cx: Scope, title: String) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
> While the new Component is shorter and easier to read, this macro should not be used by library authors since you have less control over Prop documentation.
|
|
@ -1,227 +0,0 @@
|
|||
# Introduction to Components
|
||||
|
||||
In the previous chapter, we learned about Elements and how they can be composed to create a basic User Interface. In this chapter, we'll learn how to group Elements together to form Components.
|
||||
|
||||
In this chapter, we'll learn:
|
||||
- What makes a Component
|
||||
- How to model a component and its properties in Dioxus
|
||||
- How to "think declaratively"
|
||||
|
||||
## What is a component?
|
||||
|
||||
In short, a component is a special function that takes input properties and outputs an Element. Typically, Components serve a single purpose: group functionality of a User Interface. Much like a function encapsulates some specific computation task, a Component encapsulates some specific rendering task.
|
||||
|
||||
### Learning through prior art
|
||||
|
||||
Let's take a look at a post on r/rust and see if we can sketch out a component representation.
|
||||
|
||||
![Reddit Post](../images/reddit_post.png)
|
||||
|
||||
This component has a bunch of important information:
|
||||
|
||||
- The score
|
||||
- The number of comments
|
||||
- How long ago it was posted
|
||||
- The url short address
|
||||
- The title
|
||||
- The username of the original poster
|
||||
|
||||
If we wanted to sketch out these requirements in Rust, we would start with a struct:
|
||||
|
||||
```rust
|
||||
struct PostData {
|
||||
score: i32,
|
||||
comment_count: u32,
|
||||
post_time: Instant,
|
||||
url: String,
|
||||
title: String,
|
||||
original_poster_name: String
|
||||
}
|
||||
```
|
||||
|
||||
If we look at the layout of the component, we notice quite a few buttons and pieces of functionality:
|
||||
|
||||
- Upvote/Downvote
|
||||
- View comments
|
||||
- Share
|
||||
- Save
|
||||
- Hide
|
||||
- Give award
|
||||
- Report
|
||||
- Crosspost
|
||||
- Filter by site
|
||||
- View article
|
||||
- Visit user
|
||||
|
||||
If we included all this functionality in one `rsx!` call it would be huge! Instead, let's break the post down into some core pieces:
|
||||
|
||||
![Post as Component](../images/reddit_post_components.png)
|
||||
|
||||
- **VoteButton**: Upvote/Downvote
|
||||
- **TitleCard**: Title, Filter-By-Url
|
||||
- **MetaCard**: Original Poster, Time Submitted
|
||||
- **ActionCard**: View comments, Share, Save, Hide, Give award, Report, Crosspost
|
||||
|
||||
### Modeling with Dioxus
|
||||
|
||||
We can start by sketching out the Element hierarchy using Dioxus. In general, our "Post" component will be comprised of the four sub-components listed above. First, let's define our `Post` component.
|
||||
|
||||
Unlike normal functions, Dioxus components must explicitly define a single struct to contain all the inputs. These are commonly called "Properties" (props). Our component will be a combination of these properties and a function to render them.
|
||||
|
||||
Our props must implement the `Props` trait and - if the component does not borrow any data - `PartialEq`. Both of these can be done automatically through derive macros:
|
||||
```rust
|
||||
#[derive(Props, PartialEq)]
|
||||
struct PostProps {
|
||||
id: Uuid,
|
||||
score: i32,
|
||||
comment_count: u32,
|
||||
post_time: Instant,
|
||||
url: String,
|
||||
title: String,
|
||||
original_poster: String
|
||||
}
|
||||
```
|
||||
|
||||
And our render function:
|
||||
```rust
|
||||
fn Post(cx: Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
div { class: "post-container"
|
||||
VoteButton {
|
||||
score: cx.props.score,
|
||||
}
|
||||
TitleCard {
|
||||
title: cx.props.title,
|
||||
url: cx.props.url,
|
||||
}
|
||||
MetaCard {
|
||||
original_poster: cx.props.original_poster,
|
||||
post_time: cx.props.post_time,
|
||||
}
|
||||
ActionCard {
|
||||
post_id: cx.props.id
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
When declaring a component in `rsx!`, we can pass in properties using the traditional Rust struct syntax. Dioxus will automatically call "into" on the property fields, cloning when necessary. Our `Post` component is simply a collection of smaller components wrapped together in a single container.
|
||||
|
||||
Let's take a look at the `VoteButton` component. For now, we won't include any interactivity - just the rendering the score and buttons to the screen.
|
||||
|
||||
Most of your Components will look exactly like this: a Props struct and a render function. Every component must take a `Scope` generic over some `Props` and return an `Element`.
|
||||
|
||||
As covered before, we'll build our User Interface with the `rsx!` macro and HTML tags. However, with components, we must actually "render" our HTML markup. Calling `cx.render` converts our "lazy" `rsx!` structure into an `Element`.
|
||||
|
||||
```rust
|
||||
#[derive(PartialEq, Props)]
|
||||
struct VoteButtonProps {
|
||||
score: i32
|
||||
}
|
||||
|
||||
fn VoteButton(cx: Scope<VoteButtonProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
div { class: "votebutton"
|
||||
div { class: "arrow up" }
|
||||
div { class: "score", "{cx.props.score}"}
|
||||
div { class: "arrow down" }
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Borrowing
|
||||
|
||||
You can avoid clones by using borrowed component syntax. For example, let's say we passed the `TitleCard` title as an `&str` instead of `String`. In JavaScript, the string would be copied by reference - none of the contents would be copied, but rather the reference to the string's contents are copied. In Rust, this would be similar to calling `clone` on `Rc<str>`.
|
||||
|
||||
Because we're working in Rust, we can choose to either use `Rc<str>`, clone `Title` on every re-render of `Post`, or simply borrow it. In most cases, you'll just want to let `Title` be cloned.
|
||||
|
||||
To enable borrowed values for your component, we need to add a lifetime to let the Rust compiler know that the output `Element` borrows from the component's props.
|
||||
|
||||
```rust
|
||||
#[derive(Props)]
|
||||
struct TitleCardProps<'a> {
|
||||
title: &'a str,
|
||||
}
|
||||
|
||||
fn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{cx.props.title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
For users of React: Dioxus knows *not* to memoize components that borrow property fields. By default, every component in Dioxus is memoized. This can be disabled by the presence of a non-`'static` borrow.
|
||||
|
||||
This means that during the render process, a newer version of `TitleCardProps` will never be compared with a previous version, saving some clock cycles.
|
||||
|
||||
## The inline_props macro
|
||||
|
||||
Yes - *another* macro! However, this one is entirely optional.
|
||||
|
||||
For internal components, we provide the `inline_props` macro, which will let you embed your `Props` definition right into the function arguments of your component.
|
||||
|
||||
Our title card above would be transformed from:
|
||||
|
||||
```rust
|
||||
#[derive(Props, PartialEq)]
|
||||
struct TitleCardProps {
|
||||
title: String,
|
||||
}
|
||||
|
||||
fn TitleCard(cx: Scope<TitleCardProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{cx.props.title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```rust
|
||||
#[inline_props]
|
||||
fn TitleCard(cx: Scope, title: String) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Again, this macro is optional and should not be used by library authors since you have less fine-grained control over documentation and optionality.
|
||||
|
||||
However, it's great for quickly throwing together an app without dealing with *any* extra boilerplate.
|
||||
|
||||
## The `Scope` object
|
||||
|
||||
Though very similar to React, Dioxus is different in a few ways. Most notably, React components will not have a `Scope` parameter in the component declaration.
|
||||
|
||||
Have you ever wondered how the `useState()` call works in React without a `this` object to actually store the state?
|
||||
|
||||
React uses global variables to store this information. Global mutable variables must be carefully managed and are broadly discouraged in Rust programs.
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
let [state, set_state] = useState(10);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Because Dioxus needs to work with the rules of Rust it uses the `Scope` object to maintain some internal bookkeeping. That's what the `Scope` object is: a place for the component to store state, manage listeners, and allocate elements. Advanced users of Dioxus will want to learn how to properly leverage the `Scope` object to build robust and performant extensions for Dioxus.
|
||||
|
||||
```rust
|
||||
fn Post(cx: Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!("hello"))
|
||||
}
|
||||
```
|
||||
## Moving forward
|
||||
|
||||
Next chapter, we'll talk about composing Elements and Components across files to build a larger Dioxus App.
|
||||
|
||||
For more references on components, make sure to check out:
|
||||
|
||||
- [Components in depth]()
|
||||
- [Lifecycles]()
|
||||
- [The Scope object]()
|
||||
- [Optional Prop fields]()
|
||||
|
|
@ -67,7 +67,7 @@ fn App(cx: Scope)-> Element {
|
|||
}
|
||||
```
|
||||
|
||||
Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type. To turn our `rsx!` into Elements, we need to call `cx.render`.
|
||||
Do note: the `rsx!` macro does not return an Element, but rather a wrapper struct for a `Closure` (an anonymous function). To turn our `rsx!` into an Element, we need to call `cx.render`.
|
||||
|
||||
To make patterns like these less verbose, the `rsx!` macro accepts an optional first argument on which it will call `render`. Our previous component can be shortened with this alternative syntax:
|
||||
|
||||
|
@ -136,34 +136,6 @@ cx.render(rsx!{
|
|||
})
|
||||
```
|
||||
|
||||
|
||||
## Boolean Mapping
|
||||
|
||||
In the spirit of highly-functional apps, we suggest using the "boolean mapping" pattern when trying to conditionally hide/show an Element.
|
||||
|
||||
By default, Rust lets you convert any `boolean` into any other type by calling `and_then()`. We can exploit this functionality in components by mapping to some Element.
|
||||
|
||||
```rust
|
||||
let show_title = true;
|
||||
rsx!(
|
||||
div {
|
||||
show_title.and_then(|| rsx!{
|
||||
"This is the title"
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
We can use this pattern for many things, including options:
|
||||
```rust
|
||||
let user_name = Some("bob");
|
||||
rsx!(
|
||||
div {
|
||||
user_name.map(|name| rsx!("Hello {name}"))
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Rendering Nothing
|
||||
|
||||
Sometimes, you don't want your component to return anything at all. Under the hood, the `Element` type is just an alias for `Option<VNode>`, so you can simply return `None`.
|
||||
|
@ -176,11 +148,37 @@ fn demo(cx: Scope) -> Element {
|
|||
}
|
||||
```
|
||||
|
||||
## Boolean Mapping
|
||||
|
||||
In the spirit of highly-functional apps, we suggest using the "boolean mapping" pattern when trying to conditionally hide/show an Element.
|
||||
|
||||
By default, Rust lets you convert any `boolean` into an Option of any other type with [`then()`](https://doc.rust-lang.org/std/primitive.bool.html#method.then). We can use this in Components by mapping to some Element.
|
||||
|
||||
```rust
|
||||
let show_title = true;
|
||||
rsx!(
|
||||
div {
|
||||
// Renders nothing by returning None when show_title is false
|
||||
show_title.then(|| rsx!{
|
||||
"This is the title"
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
We can use this pattern for many things, including options:
|
||||
```rust
|
||||
let user_name = Some("bob");
|
||||
rsx!(
|
||||
div {
|
||||
// Renders nothing if user_name is None
|
||||
user_name.map(|name| rsx!("Hello {name}"))
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Moving Forward:
|
||||
|
||||
In this chapter, we learned how to render different Elements from a Component depending on a condition. This is a very powerful building block to assemble complex User Interfaces!
|
||||
In this chapter, we learned how to render different Elements from a Component depending on a condition. This is a very powerful building block to assemble complex user interfaces!
|
||||
|
||||
In the next chapter, we'll cover how to renderer lists inside your `rsx!`.
|
||||
|
||||
Related Reading:
|
||||
- [RSX in Depth]()
|
||||
|
|
|
@ -68,6 +68,6 @@ Remember: this concept is not new! Many frameworks are declarative - with React
|
|||
|
||||
Here's some reading about declaring UI in React:
|
||||
|
||||
- [https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js](https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js)
|
||||
- [Difference between declarative and imperative in React.js](https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js), a StackOverflow thread
|
||||
|
||||
- [https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44](https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44)
|
||||
- [Declarative vs Imperative](https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44), a blog post by Myung Kim
|
||||
|
|
|
@ -10,14 +10,24 @@ In this chapter, you will learn:
|
|||
|
||||
## Rendering data from lists
|
||||
|
||||
Thinking back to our analysis of the `r/reddit` page, we notice a list of data that needs to be rendered: the list of posts. This list of posts is always changing, so we cannot just hardcode the lists into our app like:
|
||||
If we wanted to build the Reddit app, then we need to implement a list of data that needs to be rendered: the list of posts. This list of posts is always changing, so we cannot just hardcode the lists into our app directly, like so:
|
||||
|
||||
```rust
|
||||
// we shouldn't ship our app with posts that don't update!
|
||||
rsx!(
|
||||
div {
|
||||
Post {/* some properties */}
|
||||
Post {/* some properties */}
|
||||
Post {/* some properties */}
|
||||
Post {
|
||||
title: "Post A",
|
||||
votes: 120,
|
||||
}
|
||||
Post {
|
||||
title: "Post B",
|
||||
votes: 14,
|
||||
}
|
||||
Post {
|
||||
title: "Post C",
|
||||
votes: 999,
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
@ -32,7 +42,7 @@ As a simple example, let's render a list of names. First, start with our input d
|
|||
let names = ["jim", "bob", "jane", "doe"];
|
||||
```
|
||||
|
||||
Then, we create a new iterator by calling `iter` and then `map`. In our `map` function, we'll place render our template.
|
||||
Then, we create a new iterator by calling `iter` and then [`map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map). In our `map` function, we'll render our template.
|
||||
|
||||
```rust
|
||||
let name_list = names.iter().map(|name| rsx!(
|
||||
|
@ -40,16 +50,28 @@ let name_list = names.iter().map(|name| rsx!(
|
|||
));
|
||||
```
|
||||
|
||||
Finally, we can include this list in the final structure:
|
||||
We can include this list in the final Element:
|
||||
|
||||
```rust
|
||||
rsx!(
|
||||
ul {
|
||||
{name_list}
|
||||
name_list
|
||||
}
|
||||
)
|
||||
```
|
||||
The HTML-rendered version of this list would follow what you would expect:
|
||||
|
||||
Rather than storing `name_list` in a temporary variable, we could also include the iterator inline:
|
||||
```rust
|
||||
rsx!(
|
||||
ul {
|
||||
names.iter().map(|name| rsx!(
|
||||
li { "{name}" }
|
||||
))
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The rendered HTML list is what you would expect:
|
||||
```html
|
||||
<ul>
|
||||
<li> jim </li>
|
||||
|
@ -59,54 +81,13 @@ The HTML-rendered version of this list would follow what you would expect:
|
|||
</ul>
|
||||
```
|
||||
|
||||
### Rendering our posts with a PostList component
|
||||
|
||||
Let's start by modeling this problem with a component and some properties.
|
||||
|
||||
For this example, we're going to use the borrowed component syntax since we probably have a large list of posts that we don't want to clone every time we render the Post List.
|
||||
|
||||
```rust
|
||||
#[derive(Props, PartialEq)]
|
||||
struct PostListProps<'a> {
|
||||
posts: &'a [PostData]
|
||||
}
|
||||
```
|
||||
Next, we're going to define our component:
|
||||
|
||||
```rust
|
||||
fn App(cx: Scope<PostList>) -> Element {
|
||||
// First, we create a new iterator by mapping the post array
|
||||
let posts = cx.props.posts.iter().map(|post| rsx!{
|
||||
Post {
|
||||
title: post.title,
|
||||
age: post.age,
|
||||
original_poster: post.original_poster
|
||||
}
|
||||
});
|
||||
|
||||
// Finally, we render the post list inside of a container
|
||||
cx.render(rsx!{
|
||||
ul { class: "post-list"
|
||||
{posts}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Filtering Iterators
|
||||
|
||||
Rust's iterators are extremely powerful, especially when used for filtering tasks. When building user interfaces, you might want to display a list of items filtered by some arbitrary check.
|
||||
|
||||
As a very simple example, let's set up a filter where we only list names that begin with the letter "J".
|
||||
As a very simple example, let's set up a filter where we only list names that begin with the letter "j".
|
||||
|
||||
Let's make our list of names:
|
||||
|
||||
```rust
|
||||
let names = ["jim", "bob", "jane", "doe"];
|
||||
```
|
||||
|
||||
Then, we create a new iterator by calling `iter`, then `filter`, then `map`. In our `filter` function, we'll only allow "j" names, and in our `map` function, we'll render our template.
|
||||
Using the list from above, let's create a new iterator. Before we render the list with `map` as in the previous example, we'll [`filter`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter) the names to only allow those that start with "j".
|
||||
|
||||
```rust
|
||||
let name_list = names
|
||||
|
@ -115,53 +96,43 @@ let name_list = names
|
|||
.map(|name| rsx!( li { "{name}" }));
|
||||
```
|
||||
|
||||
Rust's iterators provide us tons of functionality and are significantly easier to work with than JavaScript's map/filter/reduce.
|
||||
Rust's Iterators are very versatile – check out [their documentation](https://doc.rust-lang.org/std/iter/trait.Iterator.html) for more things you can do with them!
|
||||
|
||||
For keen Rustaceans: notice how we don't actually call `collect` on the name list. If we `collected` our filtered list into new Vec, then we would need to make an allocation to store these new elements. Instead, we create an entirely new _lazy_ iterator which will then be consumed by Dioxus in the `render` call.
|
||||
For keen Rustaceans: notice how we don't actually call `collect` on the name list. If we `collect`ed our filtered list into new Vec, we would need to make an allocation to store these new elements, which slows down rendering. Instead, we create an entirely new _lazy_ iterator which Dioxus will consume in the `render` call. The `render` method is extraordinarily efficient, so it's best practice to let it do most of the allocations for us.
|
||||
|
||||
The `render` method is extraordinarily efficient, so it's best practice to let it do most of the allocations for us.
|
||||
## Keeping list items in order with `key`
|
||||
|
||||
## Keeping list items in order with key
|
||||
The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: if your array items move (e.g. due to sorting), get inserted, or get deleted, Dioxus has no way of knowing what happened. This can cause Elements to be unnecessarily removed, changed and rebuilt when all that was needed was to change their position – this is inneficient.
|
||||
|
||||
|
||||
The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: a lack of "keys". Whenever you render a list of elements, each item in the list must be **uniquely identifiable**. To make each item unique, you need to give it a "key".
|
||||
|
||||
In Dioxus, keys are strings that uniquely identifies it among other items in that array:
|
||||
To solve this problem, each item in the list must be **uniquely identifiable**. You can achieve this by giving it a unique, fixed "key". In Dioxus, a key is a string that identifies an item among others in the list.
|
||||
|
||||
```rust
|
||||
rsx!( li { key: "a" } )
|
||||
```
|
||||
|
||||
Keys tell Dioxus which array item each component corresponds to, so that it can match them up later. This becomes important if your array items can move (e.g. due to sorting), get inserted, or get deleted. A well-chosen key helps Dioxus infer what exactly has happened, and make the correct updates to the screen
|
||||
|
||||
Now, if an item has already been rendered once, Dioxus can use the key to match it up later to make the correct updates – and avoid unnecessary work.
|
||||
|
||||
NB: the language from this section is strongly borrowed from [React's guide on keys](https://reactjs.org/docs/lists-and-keys.html).
|
||||
|
||||
### Where to get your key
|
||||
|
||||
Different sources of data provide different sources of keys:
|
||||
|
||||
- _Data from a database_: If your data is coming from a database, you can use the database keys/IDs, which are unique by nature.
|
||||
- _Locally generated data_: If your data is generated and persisted locally (e.g. notes in a note-taking app), use an incrementing counter or a package like `uuid` when creating items.
|
||||
- _Locally generated data_: If your data is generated and persisted locally (e.g. notes in a note-taking app), keep track of keys along with your data. You can use an incrementing counter or a package like `uuid` to generate keys for new items – but make sure they stay the same for the item's lifetime.
|
||||
|
||||
Remember: keys let Dioxus uniquely identify an item among its siblings. A well-chosen key provides more information than the position within the array. Even if the position changes due to reordering, the key lets Dioxus identify the item throughout its lifetime.
|
||||
|
||||
### Rules of keys
|
||||
|
||||
- Keys must be unique among siblings. However, it’s okay to use the same keys for Elements in different arrays.
|
||||
- Keys must not change or that defeats their purpose! Don’t generate them while rendering.
|
||||
- An item's key must not change – **don’t generate them on the fly** while rendering. Otherwise, Dioxus will be unable to keep track of which item is which, and we're back to square one.
|
||||
|
||||
### Why does Dioxus need keys?
|
||||
You might be tempted to use an item's index in the array as its key. In fact, that’s what Dioxus will use if you don’t specify a key at all. This is only acceptable if you can guarantee that the list is constant – i.e., no re-ordering, additions or deletions. In all other cases, do not use the index for the key – it will lead to the performance problems described above.
|
||||
|
||||
Imagine that files on your desktop didn’t have names. Instead, you’d refer to them by their order — the first file, the second file, and so on. You could get used to it, but once you delete a file, it would get confusing. The second file would become the first file, the third file would be the second file, and so on.
|
||||
|
||||
File names in a folder and Element keys in an array serve a similar purpose. They let us uniquely identify an item between its siblings. A well-chosen key provides more information than the position within the array. Even if the position changes due to reordering, the key lets Dioxus identify the item throughout its lifetime.
|
||||
|
||||
### Gotcha
|
||||
You might be tempted to use an item’s index in the array as its key. In fact, that’s what Dioxus will use if you don’t specify a key at all. But the order in which you render items will change over time if an item is inserted, deleted, or if the array gets reordered. Index as a key often leads to subtle and confusing bugs.
|
||||
|
||||
Similarly, do not generate keys on the fly, `gen_random`. This will cause keys to never match up between renders, leading to all your components and DOM being recreated every time. Not only is this slow, but it will also lose any user input inside the list items. Instead, use a stable ID based on the data.
|
||||
|
||||
Note that your components won’t receive key as a prop. It’s only used as a hint by Dioxus itself. If your component needs an ID, you have to pass it as a separate prop:
|
||||
Note that if you pass the key to a [custom component](../components/index.md) you've made, it won't receive the key as a prop. It’s only used as a hint by Dioxus itself. If your component needs an ID, you have to pass it as a separate prop:
|
||||
```rust
|
||||
Post { key: "{key}", id: "{id}" }
|
||||
Post { key: "{key}", id: "{key}" }
|
||||
```
|
||||
|
||||
## Moving on
|
||||
|
@ -171,4 +142,4 @@ In this section, we learned:
|
|||
- How to use iterator tools to filter and transform data
|
||||
- How to use keys to render lists efficiently
|
||||
|
||||
Moving forward, we'll finally cover user input and interactivity.
|
||||
Moving forward, we'll learn more about attributes.
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# The Props Macro
|
192
docs/guide/src/elements/special_attributes.md
Normal file
192
docs/guide/src/elements/special_attributes.md
Normal file
|
@ -0,0 +1,192 @@
|
|||
# Special Attributes
|
||||
|
||||
Dioxus tries its hardest to stay close to React, but there are some divergences and "special behavior" that you should review before moving on.
|
||||
|
||||
In this section, we'll cover special attributes built into Dioxus:
|
||||
|
||||
- `dangerous_inner_html`
|
||||
- Boolean attributes
|
||||
- `prevent_default`
|
||||
<!-- - `..Attributes` -->
|
||||
- event handlers as string attributes
|
||||
- `value`, `checked`, and `selected`
|
||||
|
||||
## The HTML escape hatch: `dangerous_inner_html`
|
||||
|
||||
One thing you might've missed from React is the ability to render raw HTML directly to the DOM. If you're working with pre-rendered assets, output from templates, or output from a JS library, then you might want to pass HTML directly instead of going through Dioxus. In these instances, reach for `dangerous_inner_html`.
|
||||
|
||||
For example, shipping a markdown-to-Dioxus converter might significantly bloat your final application size. Instead, you'll want to pre-render your markdown to HTML and then include the HTML directly in your output. We use this approach for the [Dioxus homepage](https://dioxuslabs.com):
|
||||
|
||||
|
||||
```rust
|
||||
fn BlogPost(cx: Scope) -> Element {
|
||||
let contents = include_str!("../post.html");
|
||||
cx.render(rsx!{
|
||||
div {
|
||||
class: "markdown",
|
||||
dangerous_inner_html: "{contents}",
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
> Note! This attribute is called "dangerous_inner_html" because it is **dangerous** to pass it data you don't trust. If you're not careful, you can easily expose cross-site-scripting (XSS) attacks to your users.
|
||||
>
|
||||
> If you're handling untrusted input, make sure to sanitize your HTML before passing it into `dangerous_inner_html` – or just pass it to a Text Element to escape any HTML tags.
|
||||
|
||||
|
||||
## Boolean Attributes
|
||||
|
||||
Most attributes, when rendered, will be rendered exactly as the input you provided. However, some attributes are considered "boolean" attributes and just their presence determines whether or not they affect the output. For these attributes, a provided value of `"false"` will cause them to be removed from the target element.
|
||||
|
||||
So this RSX:
|
||||
|
||||
```rust
|
||||
rsx!{
|
||||
div {
|
||||
hidden: "false",
|
||||
"hello"
|
||||
}
|
||||
}
|
||||
```
|
||||
wouldn't actually render the `hidden` attribute:
|
||||
```html
|
||||
<div>hello</div>
|
||||
```
|
||||
|
||||
Not all attributes work like this however. *Only the following attributes* have this behavior:
|
||||
|
||||
- `allowfullscreen`
|
||||
- `allowpaymentrequest`
|
||||
- `async`
|
||||
- `autofocus`
|
||||
- `autoplay`
|
||||
- `checked`
|
||||
- `controls`
|
||||
- `default`
|
||||
- `defer`
|
||||
- `disabled`
|
||||
- `formnovalidate`
|
||||
- `hidden`
|
||||
- `ismap`
|
||||
- `itemscope`
|
||||
- `loop`
|
||||
- `multiple`
|
||||
- `muted`
|
||||
- `nomodule`
|
||||
- `novalidate`
|
||||
- `open`
|
||||
- `playsinline`
|
||||
- `readonly`
|
||||
- `required`
|
||||
- `reversed`
|
||||
- `selected`
|
||||
- `truespeed`
|
||||
|
||||
For any other attributes, a value of `"false"` will be sent directly to the DOM.
|
||||
|
||||
## Stopping form input and navigation with `prevent_default`
|
||||
|
||||
Currently, calling `prevent_default` on events in EventHandlers is not possible from Desktop/Mobile. Until this is supported, it's possible to prevent default using the `prevent_default` attribute.
|
||||
|
||||
> Note: you cannot conditionally prevent default with this approach. This is a limitation until synchronous event handling is available across the Webview boundary
|
||||
|
||||
To use `prevent_default`, simply attach the `prevent_default` attribute to a given element and set it to the name of the event handler you want to prevent default on. We can attach this attribute multiple times for multiple attributes.
|
||||
|
||||
```rust
|
||||
rsx!{
|
||||
input {
|
||||
oninput: move |_| {},
|
||||
prevent_default: "oninput",
|
||||
|
||||
onclick: move |_| {},
|
||||
prevent_default: "onclick",
|
||||
}
|
||||
}
|
||||
```
|
||||
<!--
|
||||
## Passing attributes into children: `..Attributes`
|
||||
|
||||
> Note: this is an experimental, unstable feature not available in released versions of Dioxus. Feel free to skip this section.
|
||||
|
||||
Just like Dioxus supports spreading component props into components, we also support spreading attributes into elements. This lets you pass any arbitrary attributes through components into elements.
|
||||
|
||||
|
||||
```rust
|
||||
#[derive(Props)]
|
||||
pub struct InputProps<'a> {
|
||||
pub children: Element<'a>,
|
||||
pub attributes: Attribute<'a>
|
||||
}
|
||||
|
||||
pub fn StateInput<'a>(cx: Scope<'a, InputProps<'a>>) -> Element {
|
||||
cx.render(rsx! (
|
||||
input {
|
||||
..cx.props.attributes,
|
||||
&cx.props.children,
|
||||
}
|
||||
))
|
||||
}
|
||||
``` -->
|
||||
|
||||
## Controlled inputs and `value`, `checked`, and `selected`
|
||||
|
||||
In Dioxus, there is a distinction between controlled and uncontrolled inputs. Most inputs you'll use are controlled, meaning we both drive the `value` of the input and react to the `oninput`.
|
||||
|
||||
Controlled components:
|
||||
```rust
|
||||
let value = use_state(&cx, || String::from("hello world"));
|
||||
|
||||
rsx! {
|
||||
input {
|
||||
oninput: move |evt| value.set(evt.value.clone()),
|
||||
value: "{value}",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With uncontrolled inputs, we won't actually drive the value from the component. This has its advantages when we don't want to re-render the component when the user inputs a value. We could either select the element directly - something Dioxus doesn't support across platforms - or we could handle `oninput` and modify a value without causing an update:
|
||||
|
||||
```rust
|
||||
let value = use_ref(&cx, || String::from("hello world"));
|
||||
|
||||
rsx! {
|
||||
input {
|
||||
oninput: move |evt| *value.write_silent() = evt.value.clone(),
|
||||
// no "value" is driven here – the input keeps track of its own value, and you can't change it
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Strings for handlers like `onclick`
|
||||
|
||||
For element fields that take a handler like `onclick` or `oninput`, Dioxus will let you attach a closure. Alternatively, you can also pass a string using normal attribute syntax and assign this attribute on the DOM.
|
||||
|
||||
This lets you use JavaScript (only if your renderer can execute JavaScript).
|
||||
|
||||
```rust
|
||||
rsx!{
|
||||
div {
|
||||
// handle oninput with rust
|
||||
oninput: move |_| {},
|
||||
|
||||
// or handle oninput with javascript
|
||||
oninput: "alert('hello world')",
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Wrapping up
|
||||
|
||||
In this chapter, we learned:
|
||||
- How to declare elements
|
||||
- How to conditionally render parts of your UI
|
||||
- How to render lists
|
||||
- Which attributes are "special"
|
||||
|
||||
<!-- todo
|
||||
There's more to elements! For further reading, check out:
|
||||
|
||||
- [Custom Elements]()
|
||||
-->
|
|
@ -56,7 +56,7 @@ With the default configuration, any Element defined within the `dioxus-html` cra
|
|||
|
||||
## Text Elements
|
||||
|
||||
Dioxus also supports a special type of Element: Text. Text Elements do not accept children, but rather just string literals denoted with double quotes.
|
||||
Dioxus also supports a special type of Element: Text. Text Elements do not accept children, just a string literal denoted by double quotes.
|
||||
|
||||
```rust
|
||||
rsx! (
|
||||
|
@ -74,20 +74,20 @@ rsx! (
|
|||
)
|
||||
```
|
||||
|
||||
Text can also be formatted with any value that implements `Display`. We use [f-string formatting](https://docs.rs/fstrings/0.2.3/fstrings/) - a "coming soon" feature for stable Rust that is familiar for Python and JavaScript users:
|
||||
Text can also be formatted with any value that implements `Display`. We use the same syntax as Rust [format strings](https://www.rustnote.com/blog/format_strings.html) – which will already be familiar for Python and JavaScript users:
|
||||
|
||||
```rust
|
||||
let name = "Bob";
|
||||
rsx! ( "hello {name}" )
|
||||
```
|
||||
|
||||
Unfortunately, you cannot drop in arbitrary expressions directly into the string literal. In the cases where we need to compute a complex value, we'll want to use `format_args!` directly. Due to specifics of how the `rsx!` macro (we'll cover later), our call to `format_args` must be contained within curly braces *and* square braces.
|
||||
Unfortunately, you cannot drop in arbitrary expressions directly into the string literal. In the cases where we need to compute a complex value, we'll want to use `format_args!` directly. Due to specifics of the `rsx!` macro (which we'll cover later), our call to `format_args` must be contained within square braces.
|
||||
|
||||
```rust
|
||||
rsx!( {[format_args!("Hello {}", if enabled { "Jack" } else { "Bob" } )]} )
|
||||
rsx!( [format_args!("Hello {}", if enabled { "Jack" } else { "Bob" } )] )
|
||||
```
|
||||
|
||||
Alternatively, `&str` can be included directly, though it must be inside of an array:
|
||||
Alternatively, `&str` can be included directly, though it must also be inside square braces:
|
||||
|
||||
```rust
|
||||
rsx!( "Hello ", [if enabled { "Jack" } else { "Bob" }] )
|
||||
|
@ -104,7 +104,7 @@ rsx! ( "hello {name}" )
|
|||
|
||||
## Attributes
|
||||
|
||||
Every Element in your User Interface will have some sort of properties that the renderer will use when drawing to the screen. These might inform the renderer if the component should be hidden, what its background color should be, or to give it a specific name or ID.
|
||||
Every Element in your user interface will have some sort of properties that the renderer will use when drawing to the screen. These might inform the renderer if the component should be hidden, what its background color should be, or to give it a specific name or ID.
|
||||
|
||||
To do this, we use the familiar struct-style syntax that Rust provides:
|
||||
|
||||
|
@ -123,7 +123,7 @@ Each field is defined as a method on the element in the `dioxus-html` crate. Thi
|
|||
1) file an issue if the attribute _should_ be enabled
|
||||
2) add a custom attribute on-the-fly
|
||||
|
||||
To use custom attributes, simply put the attribute name in quotes followed by a colon:
|
||||
To use custom attributes, simply put the attribute name in quotes:
|
||||
|
||||
```rust
|
||||
rsx!(
|
||||
|
@ -170,4 +170,4 @@ We learned:
|
|||
- Elements can either be a named container or text
|
||||
- Some Elements have properties that the renderer can use to draw the UI to the screen
|
||||
|
||||
Next, we'll compose Elements together to form components.
|
||||
Next, we'll compose Elements together using Rust-based logic.
|
||||
|
|
|
@ -63,7 +63,7 @@ $ cat Cargo.toml
|
|||
[package]
|
||||
name = "hello-dioxus"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -81,6 +81,13 @@ $ cargo add dioxus --features desktop
|
|||
|
||||
It's very important to add `dioxus` with the `desktop` feature for this example. The `dioxus` crate is a batteries-include crate that combines a bunch of utility crates together, ensuring compatibility of the most important parts of the ecosystem. Under the hood, the `dioxus` crate configures various renderers, hooks, debug tooling, and more. The `desktop` feature ensures the we only depend on the smallest set of required crates to compile and render.
|
||||
|
||||
If you system does not provide the `libappindicator3` library, like Debian/bullseye, you can enable the replacement `ayatana` with an additional flag:
|
||||
|
||||
```shell
|
||||
$ # On Debian/bullseye use:
|
||||
$ cargo add dioxus --features desktop --features ayatana
|
||||
```
|
||||
|
||||
If you plan to develop extensions for the `Dioxus` ecosystem, please use the `dioxus` crate with the `core` feature to limit the amount of dependencies your project brings in.
|
||||
|
||||
### Our first app
|
||||
|
@ -92,10 +99,10 @@ use dioxus::prelude::*;
|
|||
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! (
|
||||
div { "Hello, world!" }
|
||||
))
|
||||
|
@ -118,14 +125,14 @@ This initialization code launches a Tokio runtime on a helper thread where your
|
|||
|
||||
```rust
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
```
|
||||
|
||||
Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Element`.
|
||||
|
||||
```rust
|
||||
fn App(cx: Scope) -> Element {
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div { "Hello, world!" }
|
||||
})
|
||||
|
@ -143,5 +150,3 @@ For now, just know that `Scope` lets you store state with hooks and render eleme
|
|||
## Moving on
|
||||
|
||||
Congrats! You've built your first desktop application with Dioxus. Next, we're going to learn about the basics of building interactive user interfaces.
|
||||
|
||||
|
||||
|
|
BIN
docs/guide/src/images/oldnew.png
Normal file
BIN
docs/guide/src/images/oldnew.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
|
@ -1 +1,3 @@
|
|||
# Event handlers
|
||||
|
||||
> This section is currently under construction! 🏗
|
||||
|
|
|
@ -76,6 +76,78 @@ This is why hooks called out of order will fail - if we try to downcast a `Hook<
|
|||
|
||||
This pattern might seem strange at first, but it can be a significant upgrade over structs as blobs of state, which tend to be difficult to use in [Rust given the ownership system](https://rust-lang.github.io/rfcs/2229-capture-disjoint-fields.html).
|
||||
|
||||
|
||||
## Rules of hooks
|
||||
|
||||
Hooks are sensitive to how they are used. To use hooks, you must abide by the
|
||||
"rules of hooks" ([borrowed from react](https://reactjs.org/docs/hooks-rules.html)):
|
||||
|
||||
- Functions with "use_" should not be called in callbacks
|
||||
- Functions with "use_" should not be called out of order
|
||||
- Functions with "use_" should not be called in loops or conditionals
|
||||
|
||||
Examples of "no-nos" include:
|
||||
|
||||
### ❌ Nested uses
|
||||
|
||||
```rust
|
||||
// ❌ don't call use_hook or any `use_` function *inside* use_hook!
|
||||
cx.use_hook(|_| {
|
||||
let name = cx.use_hook(|_| "ads");
|
||||
})
|
||||
|
||||
// ✅ instead, move the first hook above
|
||||
let name = cx.use_hook(|_| "ads");
|
||||
cx.use_hook(|_| {
|
||||
// do something with name here
|
||||
})
|
||||
```
|
||||
|
||||
### ❌ Uses in conditionals
|
||||
```rust
|
||||
// ❌ don't call use_ in conditionals!
|
||||
if do_thing {
|
||||
let name = use_state(&cx, || 0);
|
||||
}
|
||||
|
||||
// ✅ instead, *always* call use_state but leave your logic
|
||||
let name = use_state(&cx, || 0);
|
||||
if do_thing {
|
||||
// do thing with name here
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Uses in loops
|
||||
|
||||
|
||||
```rust
|
||||
// ❌ Do not use hooks in loops!
|
||||
let mut nodes = vec![];
|
||||
|
||||
for name in names {
|
||||
let age = use_state(&cx, |_| 0);
|
||||
nodes.push(cx.render(rsx!{
|
||||
div { "{age}" }
|
||||
}))
|
||||
}
|
||||
|
||||
// ✅ Instead, consider refactoring your usecase into components
|
||||
#[inline_props]
|
||||
fn Child(cx: Scope, name: String) -> Element {
|
||||
let age = use_state(&cx, |_| 0);
|
||||
cx.render(rsx!{ div { "{age}" } })
|
||||
}
|
||||
|
||||
// ✅ Or, use a hashmap with use_ref
|
||||
```rust
|
||||
let ages = use_ref(&cx, || HashMap::new());
|
||||
|
||||
names.iter().map(|name| {
|
||||
let age = ages.get(name).unwrap();
|
||||
cx.render(rsx!{ div { "{age}" } })
|
||||
})
|
||||
```
|
||||
|
||||
## Building new Hooks
|
||||
|
||||
However, most hooks you'll interact with *don't* return an `&mut T` since this is not very useful in a real-world situation.
|
||||
|
@ -168,23 +240,15 @@ fn example(cx: Scope) -> Element {
|
|||
|
||||
```
|
||||
|
||||
|
||||
## Hooks provided by the `Dioxus-Hooks` package
|
||||
|
||||
By default, we bundle a handful of hooks in the Dioxus-Hooks package. Feel free to click on each hook to view its definition and associated documentation.
|
||||
|
||||
- [use_state](https://docs.rs/dioxus_hooks/use_state) - store state with ergonomic updates
|
||||
- [use_ref](https://docs.rs/dioxus_hooks/use_ref) - store non-clone state with a refcell
|
||||
- [use_future](https://docs.rs/dioxus_hooks/use_future) - store a future to be polled after initialization
|
||||
- [use_coroutine](https://docs.rs/dioxus_hooks/use_coroutine) - store a future that can be stopped/started/communicated with
|
||||
- [use_noderef](https://docs.rs/dioxus_hooks/use_noderef) - store a handle to the native element
|
||||
- [use_callback](https://docs.rs/dioxus_hooks/use_callback) - store a callback that implements PartialEq for memoization
|
||||
- [use_provide_context](https://docs.rs/dioxus_hooks/use_provide_context) - expose state to descendent components
|
||||
- [use_context](https://docs.rs/dioxus_hooks/use_context) - consume state provided by `use_provide_context`
|
||||
|
||||
For a more in-depth guide to building new hooks, checkout out the advanced hook building guide in the reference.
|
||||
|
||||
## Wrapping up
|
||||
|
||||
In this chapter, we learned about the mechanics and intricacies of storing state inside a component.
|
||||
|
||||
In the next chapter, we'll cover event listeners in similar depth, and how to combine the two to build interactive components.
|
||||
- [use_state](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_state.html) - store state with ergonomic updates
|
||||
- [use_ref](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_ref.html) - store non-clone state with a refcell
|
||||
- [use_future](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_future.html) - store a future to be polled after initialization
|
||||
- [use_coroutine](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_coroutine.html) - store a future that can be stopped/started/communicated with
|
||||
- [use_context_provider](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) - expose state to descendent components
|
||||
- [use_context](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) - consume state provided by `use_provide_context`
|
||||
- [use_suspense](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_suspense.html)
|
||||
|
|
127
docs/guide/src/interactivity/importanthooks.md
Normal file
127
docs/guide/src/interactivity/importanthooks.md
Normal file
|
@ -0,0 +1,127 @@
|
|||
# `use_state` and `use_ref`
|
||||
|
||||
Most components you will write in Dioxus will need to store state somehow. For local state, we provide two very convenient hooks:
|
||||
|
||||
- [use_state](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_state.html)
|
||||
- [use_ref](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_ref.html)
|
||||
|
||||
Both of these hooks are extremely powerful and flexible, so we've dedicated this section to understanding them properly.
|
||||
|
||||
> These two hooks are not the only way to store state. You can always build your own hooks!
|
||||
|
||||
## `use_state`
|
||||
|
||||
The `use_state` hook is very similar to its React counterpart. When we use it, we get two values:
|
||||
|
||||
- The value itself as an `&T`
|
||||
- A handle to set the value `&UseState<T>`
|
||||
|
||||
```rust
|
||||
let (count, set_count) = use_state(&cx, || 0);
|
||||
|
||||
// then to set the count
|
||||
set_count(count + 1)
|
||||
```
|
||||
|
||||
However, the `set_count` object is more powerful than it looks. You can use it as a closure, but you can also call methods on it to do more powerful operations.
|
||||
|
||||
For instance, we can get a handle to the current value at any time:
|
||||
|
||||
```rust
|
||||
let current: Rc<T> = set_count.current();
|
||||
```
|
||||
|
||||
Or, we can get the inner handle to set the value
|
||||
|
||||
```rust
|
||||
let setter: Rc<dyn Fn(T)> = set_count.setter();
|
||||
```
|
||||
|
||||
Or, we can set a new value using the current value as a reference:
|
||||
|
||||
```rust
|
||||
set_count.modify(|i| i + 1);
|
||||
```
|
||||
|
||||
If the value is cheaply cloneable, then we can call `with_mut` to get a mutable reference to the value:
|
||||
|
||||
```rust
|
||||
set_count.with_mut(|i| *i += 1);
|
||||
```
|
||||
|
||||
Alternatively, we can call `make_mut` which gives us a `RefMut` to the underlying value:
|
||||
|
||||
```rust
|
||||
*set_count.make_mut() += 1;
|
||||
```
|
||||
|
||||
Plus, the `set_count` handle is cloneable into async contexts, so we can use it in futures.
|
||||
|
||||
```rust
|
||||
// count up infinitely
|
||||
cx.spawn({
|
||||
to_owned![set_count];
|
||||
async move {
|
||||
loop {
|
||||
wait_ms(100).await;
|
||||
set_count.modify(|i| i + 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## `use_ref`
|
||||
|
||||
You might've noticed a fundamental limitation to `use_state`: to modify the value in-place, it must be cheaply cloneable. But what if your type is not cheap to clone?
|
||||
|
||||
In these cases, you should reach for `use_ref` which is essentially just a glorified `Rc<RefCell<T>>` (Rust [smart pointers](https://doc.rust-lang.org/book/ch15-04-rc.html)).
|
||||
|
||||
This provides us some runtime locks around our data, trading reliability for performance. For most cases though, you will find it hard to make `use_ref` crash.
|
||||
|
||||
> Note: this is *not* exactly like its React counterpart since calling `write` will cause a re-render. For more React parity, use the `write_silent` method.
|
||||
|
||||
To use the hook:
|
||||
|
||||
```rust
|
||||
let names = use_ref(&cx, || vec!["susie", "calvin"]);
|
||||
```
|
||||
|
||||
To read the hook values, use the `read()` method:
|
||||
|
||||
```rust
|
||||
cx.render(rsx!{
|
||||
ul {
|
||||
names.read().iter().map(|name| {
|
||||
rsx!{ "{name}" }
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
And then to write into the hook value, use the `write` method:
|
||||
|
||||
```rust
|
||||
names.write().push("Tiger");
|
||||
```
|
||||
|
||||
If you don't want to re-render the component when names is updated, then we can use the `write_silent` method:
|
||||
|
||||
```rust
|
||||
names.write_silent().push("Transmogrifier");
|
||||
```
|
||||
|
||||
Again, like `UseState`, the `UseRef` handle is clonable into async contexts:
|
||||
|
||||
|
||||
```rust
|
||||
// infinitely push calvin into the list
|
||||
cx.spawn({
|
||||
to_owned![names];
|
||||
async move {
|
||||
loop {
|
||||
wait_ms(100).await;
|
||||
names.write().push("Calvin");
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
|
@ -1,6 +1,6 @@
|
|||
# Adding Interactivity
|
||||
|
||||
So far, we've learned how to describe the structure and properties of our user interfaces. Unfortunately, they're static and quite a bit uninteresting. In this chapter, we're going to learn how to add interactivity through events, state, and tasks.
|
||||
So far, we've learned how to describe the structure and properties of our user interfaces. Unfortunately, they're static and quite uninteresting. In this chapter, we're going to learn how to add interactivity through events, state, and tasks.
|
||||
|
||||
## Primer on interactivity
|
||||
|
||||
|
@ -44,9 +44,9 @@ fn App(cx: Scope<PostProps>) -> Element {
|
|||
}
|
||||
```
|
||||
|
||||
State in Dioxus follows a pattern called "one-way-data-flow." As your components create new components as their children, your app's structure will eventually grow into a tree where state gets passed down from the root component into "leaves" of the tree.
|
||||
State in Dioxus follows a pattern called "one-way data-flow." As your components create new components as their children, your app's structure will eventually grow into a tree where state gets passed down from the root component into "leaves" of the tree.
|
||||
|
||||
You've probably seen the tree of UI components represented using an directed-acyclic-graph:
|
||||
You've probably seen the tree of UI components represented using a directed acyclic graph:
|
||||
|
||||
![image](../images/component_tree.png)
|
||||
|
||||
|
@ -64,7 +64,7 @@ The most common hook you'll use for storing state is `use_state`. `use_state` pr
|
|||
|
||||
```rust
|
||||
fn App(cx: Scope)-> Element {
|
||||
let post = use_state(&cx, || {
|
||||
let (post, set_post) = use_state(&cx, || {
|
||||
PostData {
|
||||
id: Uuid::new_v4(),
|
||||
score: 10,
|
||||
|
@ -84,10 +84,10 @@ fn App(cx: Scope)-> Element {
|
|||
}
|
||||
```
|
||||
|
||||
Whenever we have a new post that we want to render, we can call `set` on `post` and provide a new value:
|
||||
Whenever we have a new post that we want to render, we can call `set_post` and provide a new value:
|
||||
|
||||
```rust
|
||||
post.set(PostData {
|
||||
set_post(PostData {
|
||||
id: Uuid::new_v4(),
|
||||
score: 20,
|
||||
comment_count: 0,
|
||||
|
@ -112,11 +112,11 @@ For example, let's say we provide a button to generate a new post. Whenever the
|
|||
|
||||
```rust
|
||||
fn App(cx: Scope)-> Element {
|
||||
let post = use_state(&cx, || PostData::new());
|
||||
let (post, set_post) = use_state(&cx, || PostData::new());
|
||||
|
||||
cx.render(rsx!{
|
||||
button {
|
||||
on_click: move |_| post.set(PostData::random())
|
||||
on_click: move |_| set_post(PostData::random())
|
||||
"Generate a random post"
|
||||
}
|
||||
Post { props: &post }
|
||||
|
@ -128,7 +128,12 @@ We'll dive much deeper into event listeners later.
|
|||
|
||||
### Updating state asynchronously
|
||||
|
||||
We can also update our state outside of event listeners with `coroutines`. `Coroutines` are asynchronous blocks of our component that have the ability to cleanly interact with values, hooks, and other data in the component. Since coroutines stick around between renders, the data in them must be valid for the `'static` lifetime. We must explicitly declare which values our task will rely on to avoid the `stale props` problem common in React.
|
||||
We can also update our state outside of event listeners with `futures` and `coroutines`.
|
||||
|
||||
- `Futures` are Rust's version of promises that can execute asynchronous work by an efficient polling system. We can submit new futures to Dioxus either through `push_future` which returns a `TaskId` or with `spawn`.
|
||||
- `Coroutines` are asynchronous blocks of our component that have the ability to cleanly interact with values, hooks, and other data in the component.
|
||||
|
||||
Since coroutines and Futures stick around between renders, the data in them must be valid for the `'static` lifetime. We must explicitly declare which values our task will rely on to avoid the `stale props` problem common in React.
|
||||
|
||||
We can use tasks in our components to build a tiny stopwatch that ticks every second.
|
||||
|
||||
|
@ -136,14 +141,14 @@ We can use tasks in our components to build a tiny stopwatch that ticks every se
|
|||
|
||||
```rust
|
||||
fn App(cx: Scope)-> Element {
|
||||
let mut sec_elapsed = use_state(&cx, || 0);
|
||||
let (elapsed, set_elapsed) = use_state(&cx, || 0);
|
||||
|
||||
use_future(&cx, || {
|
||||
let mut sec_elapsed = sec_elapsed.for_async();
|
||||
to_owned![set_elapsed]; // explicitly capture this hook for use in async
|
||||
async move {
|
||||
loop {
|
||||
TimeoutFuture::from_ms(1000).await;
|
||||
sec_elapsed += 1;
|
||||
set_elapsed.modify(|i| i + 1)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -152,12 +157,10 @@ fn App(cx: Scope)-> Element {
|
|||
}
|
||||
```
|
||||
|
||||
Using asynchronous code can be difficult! This is just scratching the surface of what's possible. We have an entire chapter on using async properly in your Dioxus Apps.
|
||||
Using asynchronous code can be difficult! This is just scratching the surface of what's possible. We have an entire chapter on using async properly in your Dioxus Apps. We have an entire section dedicated to using `async` properly later in this book.
|
||||
|
||||
### How do I tell Dioxus that my state changed?
|
||||
|
||||
So far, we've only updated our state with `.set`. However, you might've noticed that we used `AddAssign` to increment the `sec_elapsed` value in our stopwatch example *without* calling set. This is because the `AddAssign` trait is implemented for `UseState<T>` (the wrapper around our value returned from `use_state`). Under the hood, whenever you try to mutate our value through `UseState`, you're actually calling `.set` which informs Dioxus that _this_ component needs to be updated on the screen.
|
||||
|
||||
Whenever you inform Dioxus that the component needs to be updated, it will "render" your component again, storing the previous and current Elements in memory. Dioxus will automatically figure out the differences between the old and the new and generate a list of edits that the renderer needs to apply to change what's on the screen. This process is called "diffing":
|
||||
|
||||
![Diffing](../images/diffing.png)
|
||||
|
@ -170,11 +173,37 @@ With these building blocks, we can craft new hooks similar to `use_state` that l
|
|||
|
||||
In general, Dioxus should be plenty fast for most use cases. However, there are some rules you should consider following to ensure your apps are quick.
|
||||
|
||||
- 1) **Don't call set—state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates.
|
||||
- 1) **Don't call set_state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates or enter an infinite loop.
|
||||
- 2) **Break your state apart into smaller sections.** Hooks are explicitly designed to "unshackle" your state from the typical model-view-controller paradigm, making it easy to reuse useful bits of code with a single function.
|
||||
- 3) **Move local state down**. Dioxus will need to re-check child components of your app if the root component is constantly being updated. You'll get best results if rapidly-changing state does not cause major re-renders.
|
||||
|
||||
<!-- todo: link when the section exists
|
||||
Don't worry - Dioxus is fast. But, if your app needs *extreme performance*, then take a look at the `Performance Tuning` in the `Advanced Guides` book.
|
||||
-->
|
||||
|
||||
## The `Scope` object
|
||||
|
||||
Though very similar to React, Dioxus is different in a few ways. Most notably, React components will not have a `Scope` parameter in the component declaration.
|
||||
|
||||
Have you ever wondered how the `useState()` call works in React without a `this` object to actually store the state?
|
||||
|
||||
```javascript
|
||||
// in React:
|
||||
function Component(props) {
|
||||
// This state persists between component renders, but where does it live?
|
||||
let [state, set_state] = useState(10);
|
||||
}
|
||||
```
|
||||
|
||||
React uses global variables to store this information. However, global mutable variables must be carefully managed and are broadly discouraged in Rust programs. Because Dioxus needs to work with the rules of Rust it uses the `Scope` rather than a global state object to maintain some internal bookkeeping.
|
||||
|
||||
That's what the `Scope` object is: a place for the Component to store state, manage listeners, and allocate elements. Advanced users of Dioxus will want to learn how to properly leverage the `Scope` object to build robust and performant extensions for Dioxus.
|
||||
|
||||
```rust
|
||||
fn Post(cx: Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!("hello"))
|
||||
}
|
||||
```
|
||||
|
||||
## Moving On
|
||||
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
# Lifecycle, updates, and effects
|
||||
|
||||
> This section is currently under construction! 🏗
|
||||
|
|
|
@ -1 +1,6 @@
|
|||
# User Input and Controlled Components
|
||||
|
||||
Handling user input is one of the most common things your app will do, but it can be tricky
|
||||
|
||||
|
||||
> This section is currently under construction! 🏗
|
||||
|
|
|
@ -8,7 +8,7 @@ We'll learn about:
|
|||
- Suggested cargo extensions
|
||||
|
||||
|
||||
For platform-specific guides, check out the [Platform Specific Guides](../platforms/00-index.md).
|
||||
For platform-specific guides, check out the [Platform Specific Guides](/reference/platforms/index.md).
|
||||
|
||||
# Setting up Dioxus
|
||||
|
||||
|
@ -19,7 +19,7 @@ Dioxus requires a few main things to get up and running:
|
|||
|
||||
Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more.
|
||||
|
||||
### Installing Rust
|
||||
## Installing Rust
|
||||
|
||||
Head over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler.
|
||||
|
||||
|
@ -29,24 +29,45 @@ Once installed, make sure to install wasm32-unknown-unknown as a target if you'r
|
|||
rustup target add wasm32-unknown-unknown
|
||||
```
|
||||
|
||||
### Platform-Specific Dependencies
|
||||
## Platform-Specific Dependencies
|
||||
|
||||
If you are running a modern, mainstream operating system, you should need no additional setup to build WebView-based Desktop apps. However, if you are running an older version of Windows or a flavor of Linux with no default web rendering engine, you might need to install some additional dependencies.
|
||||
|
||||
For windows users: download the [bootstrapper for Webview2 from Microsoft](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
|
||||
|
||||
For linux users, we need the development libraries for libgtk.
|
||||
### Windows
|
||||
|
||||
Windows Desktop apps depend on WebView2 - a library which should be installed in all modern Windows distributions. If you have Edge installed, then Dioxus will work fine. If you *don't* have Webview2, [then you can install it through Microsoft](https://developer.microsoft.com/en-us/microsoft-edge/webview2/). MS provides 3 options:
|
||||
|
||||
1. A tiny "evergreen" *bootstrapper* which will fetch an installer from Microsoft's CDN
|
||||
2. A tiny *installer* which will fetch Webview2 from Microsoft's CDN
|
||||
3. A statically linked version of Webview2 in your final binary for offline users
|
||||
|
||||
For development purposes, use Option 1.
|
||||
|
||||
### Linux
|
||||
|
||||
Webview Linux apps require WebkitGtk. When distributing, this can be part of your dependency tree in your `.rpm` or `.deb`. However, it's very likely that your users will already have WebkitGtk.
|
||||
|
||||
```
|
||||
sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev
|
||||
```
|
||||
|
||||
When distributing onto older Windows platforms or less-mainstream
|
||||
When using Debian/bullseye `libappindicator3-dev` is no longer available but replaced by `libayatana-appindicator3-dev`.
|
||||
|
||||
```
|
||||
# on Debian/bullseye use:
|
||||
sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatanta-appindicator3-dev
|
||||
```
|
||||
|
||||
If you run into issues, make sure you have all the basics installed, as outlined in the [Tauri docs](https://tauri.studio/en/docs/get-started/setup-linux).
|
||||
|
||||
|
||||
### macOS
|
||||
|
||||
Currently - everything for macOS is built right in! However, you might run into an issue if you're using nightly Rust due to some permissions issues in our Tao dependency (which have been resolved but not published).
|
||||
|
||||
|
||||
### Dioxus-CLI for dev server, bundling, etc.
|
||||
## Dioxus-CLI for dev server, bundling, etc.
|
||||
|
||||
We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. To install the CLI, you'll need cargo (which should be automatically installed with Rust):
|
||||
|
||||
|
@ -62,7 +83,7 @@ $ cargo install --force dioxus-cli
|
|||
|
||||
We provide this 1st-party tool to save you from having to run potentially untrusted code every time you add a crate to your project - as is standard in the NPM ecosystem.
|
||||
|
||||
### Suggested extensions
|
||||
## Suggested extensions
|
||||
|
||||
If you want to keep your traditional `npm install XXX` workflow for adding packages, you might want to install `cargo-edit` and a few other fun `cargo` extensions:
|
||||
|
||||
|
|
|
@ -9,3 +9,4 @@ fn App((cx, props): Component) -> Element {
|
|||
}
|
||||
```
|
||||
|
||||
> This section is currently under construction! 🏗
|
||||
|
|
|
@ -4,5 +4,25 @@ Every app you'll build with Dioxus will have some sort of state that needs to be
|
|||
|
||||
In this chapter, we'll cover the various ways to manage state, the appropriate terminology, various patterns, and then take an overview
|
||||
|
||||
## Terminology
|
||||
|
||||
|
||||
> This section is currently under construction! 🏗
|
||||
|
||||
|
||||
|
||||
|
||||
## Important hook: `use_state`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Important hook: `use_ref`
|
||||
|
||||
|
||||
|
||||
|
||||
## `provide_context` and `consume_context`
|
||||
|
||||
## Terminology
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
# Lifting State
|
||||
|
||||
|
||||
> This section is currently under construction! 🏗
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
# Local State
|
||||
|
||||
|
||||
> This section is currently under construction! 🏗
|
||||
|
|
|
@ -1 +1,7 @@
|
|||
# Global State
|
||||
|
||||
|
||||
cx.provide_context()
|
||||
cx.consume_context()
|
||||
|
||||
> This section is currently under construction! 🏗
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
# Defining Components
|
||||
|
||||
This section is currently under construction! 🏗
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
# Defining State
|
||||
|
||||
This section is currently under construction! 🏗
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
# Structuring our app
|
||||
|
||||
|
||||
This section is currently under construction! 🏗
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
# Styling
|
||||
|
||||
|
||||
This section is currently under construction! 🏗
|
||||
|
|
1
docs/nightly/readme.md
Normal file
1
docs/nightly/readme.md
Normal file
|
@ -0,0 +1 @@
|
|||
this directory is for deploying into
|
|
@ -2,11 +2,12 @@
|
|||
|
||||
- [Introduction](README.md)
|
||||
|
||||
- [Web](web/index.md)
|
||||
- [Desktop](desktop/index.md)
|
||||
- [Mobile](mobile/index.md)
|
||||
- [SSR](ssr/index.md)
|
||||
- [TUI](tui/index.md)
|
||||
- [Platforms](platforms/index.md)
|
||||
- [Web](platforms/web.md)
|
||||
- [Server Side Rendering](platforms/ssr.md)
|
||||
- [Desktop](platforms/desktop.md)
|
||||
- [Mobile](platforms/mobile.md)
|
||||
- [TUI](platforms/tui.md)
|
||||
|
||||
- [Advanced Guides](guide/index.md)
|
||||
- [RSX in Depth](guide/rsx_in_depth.md)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Desktop
|
||||
# Getting Started: Desktop
|
||||
|
||||
One of Dioxus' killer features is the ability to quickly build a native desktop app that looks and feels the same across platforms. Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory.
|
||||
|
10
docs/reference/src/platforms/index.md
Normal file
10
docs/reference/src/platforms/index.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Platforms
|
||||
|
||||
Dioxus supports many different platforms. Below are a list of guides that walk you through setting up Dioxus for a specific platform.
|
||||
|
||||
### Setup Guides
|
||||
- [Web](web.md)
|
||||
- [Server Side Rendering](ssr.md)
|
||||
- [Desktop](desktop.md)
|
||||
- [Mobile](mobile.md)
|
||||
- [TUI](tui.md)
|
|
@ -21,7 +21,7 @@ $ cargo install --git https://github.com/BrainiumLLC/cargo-mobile
|
|||
And then initialize your app for the right platform. Use the `winit` template for now. Right now, there's no "Dioxus" template in cargo-mobile.
|
||||
|
||||
```shell
|
||||
$ cargo moble init
|
||||
$ cargo mobile init
|
||||
```
|
||||
|
||||
We're going to completely clear out the `dependencies` it generates for us, swapping out `winit` with `dioxus-mobile`.
|
|
@ -2,11 +2,8 @@
|
|||
|
||||
The Dioxus VirtualDom can be rendered to a string by traversing the Element Tree. This is implemented in the `ssr` crate where your Dioxus app can be directly rendered to HTML to be served by a web server.
|
||||
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
|
||||
If you just want to render `rsx!` or a VirtualDom to HTML, check out the API docs. It's pretty simple:
|
||||
|
||||
```rust
|
||||
|
@ -19,8 +16,7 @@ println!("{}", dioxus::ssr::render_vdom(&vdom));
|
|||
println!( "{}", dioxus::ssr::render_lazy(rsx! { h1 { "Hello, world!" } } );
|
||||
```
|
||||
|
||||
|
||||
However, for this guide, we're going to show how to use Dioxus SSR with `Axum`.
|
||||
However, for this guide, we're going to show how to use Dioxus SSR with `Axum`.
|
||||
|
||||
Make sure you have Rust and Cargo installed, and then create a new project:
|
||||
|
||||
|
@ -29,15 +25,16 @@ $ cargo new --bin demo
|
|||
$ cd app
|
||||
```
|
||||
|
||||
Add Dioxus with the `desktop` feature:
|
||||
Add Dioxus with the `ssr` feature:
|
||||
|
||||
```shell
|
||||
$ cargo add dioxus --features ssr
|
||||
```
|
||||
|
||||
Next, add all the Axum dependencies. This will be different if you're using a different Web Framework
|
||||
|
||||
```
|
||||
$ cargo add dioxus tokio --features full
|
||||
$ cargo add tokio --features full
|
||||
$ cargo add axum
|
||||
```
|
||||
|
||||
|
@ -45,12 +42,11 @@ Your dependencies should look roughly like this:
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
axum = "0.4.3"
|
||||
axum = "0.4.5"
|
||||
dioxus = { version = "*", features = ["ssr"] }
|
||||
tokio = { version = "1.15.0", features = ["full"] }
|
||||
```
|
||||
|
||||
|
||||
Now, setup your Axum app to respond on an endpoint.
|
||||
|
||||
```rust
|
||||
|
@ -63,7 +59,11 @@ async fn main() {
|
|||
println!("listening on http://{}", addr);
|
||||
|
||||
axum::Server::bind(&addr)
|
||||
.serve(Router::new().route("/", get(app_endpoint)))
|
||||
.serve(
|
||||
Router::new()
|
||||
.route("/", get(app_endpoint))
|
||||
.into_make_service(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
@ -88,14 +88,14 @@ async fn app_endpoint() -> Html<String> {
|
|||
}
|
||||
let mut app = VirtualDom::new(app);
|
||||
let _ = app.rebuild();
|
||||
|
||||
|
||||
Html(dioxus::ssr::render_vdom(&app))
|
||||
}
|
||||
```
|
||||
|
||||
And that's it!
|
||||
|
||||
> You might notice that you cannot hold the VirtualDom across an await point. Dioxus is currently not ThreadSafe, so it *must* remain on the thread it started. We are working on loosening this requirement.
|
||||
> You might notice that you cannot hold the VirtualDom across an await point. Dioxus is currently not ThreadSafe, so it _must_ remain on the thread it started. We are working on loosening this requirement.
|
||||
|
||||
## Future Steps
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# TUI
|
||||
# Getting Started: TUI
|
||||
|
||||
TUI support is currently quite experimental. Even the project name will change. But, if you're willing to venture into the realm of the unknown, this guide will get you started.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Getting Started: Dioxus for Web
|
||||
# Getting Started: Web
|
||||
|
||||
[*"Pack your things, we're going on an adventure!"*](https://trunkrs.dev)
|
||||
|
1
docs/router/.gitignore
vendored
Normal file
1
docs/router/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
book
|
6
docs/router/book.toml
Normal file
6
docs/router/book.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[book]
|
||||
authors = ["Jonathan Kelley"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Dioxus Router"
|
3
docs/router/src/SUMMARY.md
Normal file
3
docs/router/src/SUMMARY.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Summary
|
||||
|
||||
- [Chapter 1](./chapter_1.md)
|
3
docs/router/src/chapter_1.md
Normal file
3
docs/router/src/chapter_1.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Chapter 1
|
||||
|
||||
The Dioxus Router is very important!!
|
411
examples/all_css.rs
Normal file
411
examples/all_css.rs
Normal file
|
@ -0,0 +1,411 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
align_content: "a",
|
||||
align_items: "a",
|
||||
align_self: "a",
|
||||
alignment_adjust: "a",
|
||||
alignment_baseline: "a",
|
||||
all: "a",
|
||||
alt: "a",
|
||||
animation: "a",
|
||||
animation_delay: "a",
|
||||
animation_direction: "a",
|
||||
animation_duration: "a",
|
||||
animation_fill_mode: "a",
|
||||
animation_iteration_count: "a",
|
||||
animation_name: "a",
|
||||
animation_play_state: "a",
|
||||
animation_timing_function: "a",
|
||||
azimuth: "a",
|
||||
backface_visibility: "a",
|
||||
background: "a",
|
||||
background_attachment: "a",
|
||||
background_clip: "a",
|
||||
background_color: "a",
|
||||
background_image: "a",
|
||||
background_origin: "a",
|
||||
background_position: "a",
|
||||
background_repeat: "a",
|
||||
background_size: "a",
|
||||
background_blend_mode: "a",
|
||||
baseline_shift: "a",
|
||||
bleed: "a",
|
||||
bookmark_label: "a",
|
||||
bookmark_level: "a",
|
||||
bookmark_state: "a",
|
||||
border: "a",
|
||||
border_color: "a",
|
||||
border_style: "a",
|
||||
border_width: "a",
|
||||
border_bottom: "a",
|
||||
border_bottom_color: "a",
|
||||
border_bottom_style: "a",
|
||||
border_bottom_width: "a",
|
||||
border_left: "a",
|
||||
border_left_color: "a",
|
||||
border_left_style: "a",
|
||||
border_left_width: "a",
|
||||
border_right: "a",
|
||||
border_right_color: "a",
|
||||
border_right_style: "a",
|
||||
border_right_width: "a",
|
||||
border_top: "a",
|
||||
border_top_color: "a",
|
||||
border_top_style: "a",
|
||||
border_top_width: "a",
|
||||
border_collapse: "a",
|
||||
border_image: "a",
|
||||
border_image_outset: "a",
|
||||
border_image_repeat: "a",
|
||||
border_image_slice: "a",
|
||||
border_image_source: "a",
|
||||
border_image_width: "a",
|
||||
border_radius: "a",
|
||||
border_bottom_left_radius: "a",
|
||||
border_bottom_right_radius: "a",
|
||||
border_top_left_radius: "a",
|
||||
border_top_right_radius: "a",
|
||||
border_spacing: "a",
|
||||
bottom: "a",
|
||||
box_decoration_break: "a",
|
||||
box_shadow: "a",
|
||||
box_sizing: "a",
|
||||
box_snap: "a",
|
||||
break_after: "a",
|
||||
break_before: "a",
|
||||
break_inside: "a",
|
||||
buffered_rendering: "a",
|
||||
caption_side: "a",
|
||||
clear: "a",
|
||||
clear_side: "a",
|
||||
clip: "a",
|
||||
clip_path: "a",
|
||||
clip_rule: "a",
|
||||
color: "a",
|
||||
color_adjust: "a",
|
||||
color_correction: "a",
|
||||
color_interpolation: "a",
|
||||
color_interpolation_filters: "a",
|
||||
color_profile: "a",
|
||||
color_rendering: "a",
|
||||
column_fill: "a",
|
||||
column_gap: "a",
|
||||
column_rule: "a",
|
||||
column_rule_color: "a",
|
||||
column_rule_style: "a",
|
||||
column_rule_width: "a",
|
||||
column_span: "a",
|
||||
columns: "a",
|
||||
column_count: "a",
|
||||
column_width: "a",
|
||||
contain: "a",
|
||||
content: "a",
|
||||
counter_increment: "a",
|
||||
counter_reset: "a",
|
||||
counter_set: "a",
|
||||
cue: "a",
|
||||
cue_after: "a",
|
||||
cue_before: "a",
|
||||
cursor: "a",
|
||||
direction: "a",
|
||||
display: "a",
|
||||
display_inside: "a",
|
||||
display_outside: "a",
|
||||
display_extras: "a",
|
||||
display_box: "a",
|
||||
dominant_baseline: "a",
|
||||
elevation: "a",
|
||||
empty_cells: "a",
|
||||
enable_background: "a",
|
||||
fill: "a",
|
||||
fill_opacity: "a",
|
||||
fill_rule: "a",
|
||||
filter: "a",
|
||||
float: "a",
|
||||
float_defer_column: "a",
|
||||
float_defer_page: "a",
|
||||
float_offset: "a",
|
||||
float_wrap: "a",
|
||||
flow_into: "a",
|
||||
flow_from: "a",
|
||||
flex: "a",
|
||||
flex_basis: "a",
|
||||
flex_grow: "a",
|
||||
flex_shrink: "a",
|
||||
flex_flow: "a",
|
||||
flex_direction: "a",
|
||||
flex_wrap: "a",
|
||||
flood_color: "a",
|
||||
flood_opacity: "a",
|
||||
font: "a",
|
||||
font_family: "a",
|
||||
font_size: "a",
|
||||
font_stretch: "a",
|
||||
font_style: "a",
|
||||
font_weight: "a",
|
||||
font_feature_settings: "a",
|
||||
font_kerning: "a",
|
||||
font_language_override: "a",
|
||||
font_size_adjust: "a",
|
||||
font_synthesis: "a",
|
||||
font_variant: "a",
|
||||
font_variant_alternates: "a",
|
||||
font_variant_caps: "a",
|
||||
font_variant_east_asian: "a",
|
||||
font_variant_ligatures: "a",
|
||||
font_variant_numeric: "a",
|
||||
font_variant_position: "a",
|
||||
footnote_policy: "a",
|
||||
glyph_orientation_horizontal: "a",
|
||||
glyph_orientation_vertical: "a",
|
||||
grid: "a",
|
||||
grid_auto_flow: "a",
|
||||
grid_auto_columns: "a",
|
||||
grid_auto_rows: "a",
|
||||
grid_template: "a",
|
||||
grid_template_areas: "a",
|
||||
grid_template_columns: "a",
|
||||
grid_template_rows: "a",
|
||||
grid_area: "a",
|
||||
grid_column: "a",
|
||||
grid_column_start: "a",
|
||||
grid_column_end: "a",
|
||||
grid_row: "a",
|
||||
grid_row_start: "a",
|
||||
grid_row_end: "a",
|
||||
hanging_punctuation: "a",
|
||||
height: "a",
|
||||
hyphenate_character: "a",
|
||||
hyphenate_limit_chars: "a",
|
||||
hyphenate_limit_last: "a",
|
||||
hyphenate_limit_lines: "a",
|
||||
hyphenate_limit_zone: "a",
|
||||
hyphens: "a",
|
||||
icon: "a",
|
||||
image_orientation: "a",
|
||||
image_resolution: "a",
|
||||
image_rendering: "a",
|
||||
ime: "a",
|
||||
ime_align: "a",
|
||||
ime_mode: "a",
|
||||
ime_offset: "a",
|
||||
ime_width: "a",
|
||||
initial_letters: "a",
|
||||
inline_box_align: "a",
|
||||
isolation: "a",
|
||||
justify_content: "a",
|
||||
justify_items: "a",
|
||||
justify_self: "a",
|
||||
kerning: "a",
|
||||
left: "a",
|
||||
letter_spacing: "a",
|
||||
lighting_color: "a",
|
||||
line_box_contain: "a",
|
||||
line_break: "a",
|
||||
line_grid: "a",
|
||||
line_height: "a",
|
||||
line_slack: "a",
|
||||
line_snap: "a",
|
||||
list_style: "a",
|
||||
list_style_image: "a",
|
||||
list_style_position: "a",
|
||||
list_style_type: "a",
|
||||
margin: "a",
|
||||
margin_bottom: "a",
|
||||
margin_left: "a",
|
||||
margin_right: "a",
|
||||
margin_top: "a",
|
||||
marker: "a",
|
||||
marker_end: "a",
|
||||
marker_mid: "a",
|
||||
marker_pattern: "a",
|
||||
marker_segment: "a",
|
||||
marker_start: "a",
|
||||
marker_knockout_left: "a",
|
||||
marker_knockout_right: "a",
|
||||
marker_side: "a",
|
||||
marks: "a",
|
||||
marquee_direction: "a",
|
||||
marquee_play_count: "a",
|
||||
marquee_speed: "a",
|
||||
marquee_style: "a",
|
||||
mask: "a",
|
||||
mask_image: "a",
|
||||
mask_repeat: "a",
|
||||
mask_position: "a",
|
||||
mask_clip: "a",
|
||||
mask_origin: "a",
|
||||
mask_size: "a",
|
||||
mask_box: "a",
|
||||
mask_box_outset: "a",
|
||||
mask_box_repeat: "a",
|
||||
mask_box_slice: "a",
|
||||
mask_box_source: "a",
|
||||
mask_box_width: "a",
|
||||
mask_type: "a",
|
||||
max_height: "a",
|
||||
max_lines: "a",
|
||||
max_width: "a",
|
||||
min_height: "a",
|
||||
min_width: "a",
|
||||
mix_blend_mode: "a",
|
||||
nav_down: "a",
|
||||
nav_index: "a",
|
||||
nav_left: "a",
|
||||
nav_right: "a",
|
||||
nav_up: "a",
|
||||
object_fit: "a",
|
||||
object_position: "a",
|
||||
offset_after: "a",
|
||||
offset_before: "a",
|
||||
offset_end: "a",
|
||||
offset_start: "a",
|
||||
opacity: "a",
|
||||
order: "a",
|
||||
orphans: "a",
|
||||
outline: "a",
|
||||
outline_color: "a",
|
||||
outline_style: "a",
|
||||
outline_width: "a",
|
||||
outline_offset: "a",
|
||||
overflow: "a",
|
||||
overflow_x: "a",
|
||||
overflow_y: "a",
|
||||
overflow_style: "a",
|
||||
overflow_wrap: "a",
|
||||
padding: "a",
|
||||
padding_bottom: "a",
|
||||
padding_left: "a",
|
||||
padding_right: "a",
|
||||
padding_top: "a",
|
||||
page: "a",
|
||||
page_break_after: "a",
|
||||
page_break_before: "a",
|
||||
page_break_inside: "a",
|
||||
paint_order: "a",
|
||||
pause: "a",
|
||||
pause_after: "a",
|
||||
pause_before: "a",
|
||||
perspective: "a",
|
||||
perspective_origin: "a",
|
||||
pitch: "a",
|
||||
pitch_range: "a",
|
||||
play_during: "a",
|
||||
pointer_events: "a",
|
||||
position: "a",
|
||||
quotes: "a",
|
||||
region_fragment: "a",
|
||||
resize: "a",
|
||||
rest: "a",
|
||||
rest_after: "a",
|
||||
rest_before: "a",
|
||||
richness: "a",
|
||||
right: "a",
|
||||
ruby_align: "a",
|
||||
ruby_merge: "a",
|
||||
ruby_position: "a",
|
||||
scroll_behavior: "a",
|
||||
scroll_snap_coordinate: "a",
|
||||
scroll_snap_destination: "a",
|
||||
scroll_snap_points_x: "a",
|
||||
scroll_snap_points_y: "a",
|
||||
scroll_snap_type: "a",
|
||||
shape_image_threshold: "a",
|
||||
shape_inside: "a",
|
||||
shape_margin: "a",
|
||||
shape_outside: "a",
|
||||
shape_padding: "a",
|
||||
shape_rendering: "a",
|
||||
size: "a",
|
||||
speak: "a",
|
||||
speak_as: "a",
|
||||
speak_header: "a",
|
||||
speak_numeral: "a",
|
||||
speak_punctuation: "a",
|
||||
speech_rate: "a",
|
||||
stop_color: "a",
|
||||
stop_opacity: "a",
|
||||
stress: "a",
|
||||
string_set: "a",
|
||||
stroke: "a",
|
||||
stroke_dasharray: "a",
|
||||
stroke_dashoffset: "a",
|
||||
stroke_linecap: "a",
|
||||
stroke_linejoin: "a",
|
||||
stroke_miterlimit: "a",
|
||||
stroke_opacity: "a",
|
||||
stroke_width: "a",
|
||||
tab_size: "a",
|
||||
table_layout: "a",
|
||||
text_align: "a",
|
||||
text_align_all: "a",
|
||||
text_align_last: "a",
|
||||
text_anchor: "a",
|
||||
text_combine_upright: "a",
|
||||
text_decoration: "a",
|
||||
text_decoration_color: "a",
|
||||
text_decoration_line: "a",
|
||||
text_decoration_style: "a",
|
||||
text_decoration_skip: "a",
|
||||
text_emphasis: "a",
|
||||
text_emphasis_color: "a",
|
||||
text_emphasis_style: "a",
|
||||
text_emphasis_position: "a",
|
||||
text_emphasis_skip: "a",
|
||||
text_height: "a",
|
||||
text_indent: "a",
|
||||
text_justify: "a",
|
||||
text_orientation: "a",
|
||||
text_overflow: "a",
|
||||
text_rendering: "a",
|
||||
text_shadow: "a",
|
||||
text_size_adjust: "a",
|
||||
text_space_collapse: "a",
|
||||
text_spacing: "a",
|
||||
text_transform: "a",
|
||||
text_underline_position: "a",
|
||||
text_wrap: "a",
|
||||
top: "a",
|
||||
touch_action: "a",
|
||||
transform: "a",
|
||||
transform_box: "a",
|
||||
transform_origin: "a",
|
||||
transform_style: "a",
|
||||
transition: "a",
|
||||
transition_delay: "a",
|
||||
transition_duration: "a",
|
||||
transition_property: "a",
|
||||
unicode_bidi: "a",
|
||||
vector_effect: "a",
|
||||
vertical_align: "a",
|
||||
visibility: "a",
|
||||
voice_balance: "a",
|
||||
voice_duration: "a",
|
||||
voice_family: "a",
|
||||
voice_pitch: "a",
|
||||
voice_range: "a",
|
||||
voice_rate: "a",
|
||||
voice_stress: "a",
|
||||
voice_volumn: "a",
|
||||
volume: "a",
|
||||
white_space: "a",
|
||||
widows: "a",
|
||||
width: "a",
|
||||
will_change: "a",
|
||||
word_break: "a",
|
||||
word_spacing: "a",
|
||||
word_wrap: "a",
|
||||
wrap_flow: "a",
|
||||
wrap_through: "a",
|
||||
writing_mode: "a",
|
||||
z_index: "a",
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch_cfg(app, |cfg| {
|
||||
cfg.with_window(|w| w.with_title("BorderLess Demo").with_decorations(false))
|
||||
});
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let window = dioxus::desktop::use_window(&cx);
|
||||
|
||||
cx.render(rsx!(
|
||||
link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" }
|
||||
header {
|
||||
class: "text-gray-400 bg-gray-900 body-font",
|
||||
onmousedown: move |_| window.drag(),
|
||||
div {
|
||||
class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
|
||||
a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
|
||||
span { class: "ml-3 text-xl", "Dioxus"}
|
||||
}
|
||||
nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" }
|
||||
button {
|
||||
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
onmousedown: |evt| evt.cancel_bubble(),
|
||||
onclick: move |_| window.minimize(true),
|
||||
"Minimize"
|
||||
}
|
||||
button {
|
||||
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
onmousedown: |evt| evt.cancel_bubble(),
|
||||
onclick: move |_| window.close(),
|
||||
"Close"
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
|
@ -16,7 +16,9 @@ struct ListBreeds {
|
|||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let breeds = use_future(&cx, || async move {
|
||||
let (breed, set_breed) = use_state(&cx, || None);
|
||||
|
||||
let breeds = use_future(&cx, (), |_| async move {
|
||||
reqwest::get("https://dog.ceo/api/breeds/list/all")
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -24,13 +26,10 @@ fn app(cx: Scope) -> Element {
|
|||
.await
|
||||
});
|
||||
|
||||
let (breed, set_breed) = use_state(&cx, || None);
|
||||
|
||||
match breeds.value() {
|
||||
Some(Ok(breeds)) => cx.render(rsx! {
|
||||
div {
|
||||
h1 {"Select a dog breed!"}
|
||||
|
||||
h1 { "Select a dog breed!" }
|
||||
div { display: "flex",
|
||||
ul { flex: "50%",
|
||||
breeds.message.keys().map(|breed| rsx!(
|
||||
|
@ -51,34 +50,23 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
}
|
||||
}),
|
||||
Some(Err(_e)) => cx.render(rsx! {
|
||||
div { "Error fetching breeds" }
|
||||
}),
|
||||
None => cx.render(rsx! {
|
||||
div { "Loading dogs..." }
|
||||
}),
|
||||
Some(Err(_e)) => cx.render(rsx! { div { "Error fetching breeds" } }),
|
||||
None => cx.render(rsx! { div { "Loading dogs..." } }),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct DogApi {
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn Breed(cx: Scope, breed: String) -> Element {
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct DogApi {
|
||||
message: String,
|
||||
}
|
||||
|
||||
let endpoint = format!("https://dog.ceo/api/breed/{}/images/random", breed);
|
||||
|
||||
let fut = use_future(&cx, || async move {
|
||||
let fut = use_future(&cx, (breed,), |(breed,)| async move {
|
||||
let endpoint = format!("https://dog.ceo/api/breed/{}/images/random", breed);
|
||||
reqwest::get(endpoint).await.unwrap().json::<DogApi>().await
|
||||
});
|
||||
|
||||
let (name, set_name) = use_state(&cx, || breed.clone());
|
||||
if name != breed {
|
||||
set_name(breed.clone());
|
||||
fut.restart();
|
||||
}
|
||||
|
||||
cx.render(match fut.value() {
|
||||
Some(Ok(resp)) => rsx! {
|
||||
button {
|
||||
|
|
30
examples/fermi.rs
Normal file
30
examples/fermi.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use fermi::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(app)
|
||||
}
|
||||
|
||||
static NAME: Atom<String> = |_| "world".to_string();
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let name = use_read(&cx, NAME);
|
||||
|
||||
cx.render(rsx! {
|
||||
div { "hello {name}!" }
|
||||
Child {}
|
||||
})
|
||||
}
|
||||
|
||||
fn Child(cx: Scope) -> Element {
|
||||
let set_name = use_set(&cx, NAME);
|
||||
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
onclick: move |_| set_name("dioxus".to_string()),
|
||||
"reset name"
|
||||
}
|
||||
})
|
||||
}
|
26
examples/form.rs
Normal file
26
examples/form.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
//! Forms
|
||||
//!
|
||||
//! Dioxus forms deviate slightly from html, automatically returning all named inputs
|
||||
//! in the "values" field
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 { "Form" }
|
||||
form {
|
||||
onsubmit: move |ev| println!("Submitted {:?}", ev.values),
|
||||
oninput: move |ev| println!("Input {:?}", ev.values),
|
||||
input { r#type: "text", name: "username" }
|
||||
input { r#type: "text", name: "full-name" }
|
||||
input { r#type: "password", name: "password" }
|
||||
button { "Submit the form" }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
28
examples/heavy_compute.rs
Normal file
28
examples/heavy_compute.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
//! This example shows that you can place heavy work on the main thread, and then
|
||||
//!
|
||||
//! You *should* be using `tokio::spawn_blocking` instead.
|
||||
//!
|
||||
//! Your app runs in an async runtime (Tokio), so you should avoid blocking
|
||||
//! the rendering of the VirtualDom.
|
||||
//!
|
||||
//!
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
// This is discouraged
|
||||
std::thread::sleep(std::time::Duration::from_millis(2_000));
|
||||
|
||||
// This is suggested
|
||||
tokio::task::spawn_blocking(move || {
|
||||
std::thread::sleep(std::time::Duration::from_millis(2_000));
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
div { "Hello, world!" }
|
||||
})
|
||||
}
|
50
examples/login_form.rs
Normal file
50
examples/login_form.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
//! This example demonstrates the following:
|
||||
//! Futures in a callback, Router, and Forms
|
||||
|
||||
use dioxus::events::*;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let onsubmit = move |evt: FormEvent| {
|
||||
cx.spawn(async move {
|
||||
let resp = reqwest::Client::new()
|
||||
.post("http://localhost:8080/login")
|
||||
.form(&[
|
||||
("username", &evt.values["username"]),
|
||||
("password", &evt.values["password"]),
|
||||
])
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match resp {
|
||||
// Parse data from here, such as storing a response token
|
||||
Ok(_data) => println!("Login successful!"),
|
||||
|
||||
//Handle any errors from the fetch here
|
||||
Err(_err) => {
|
||||
println!("Login failed - you need a login server running on localhost:8080.")
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
h1 { "Login" }
|
||||
form {
|
||||
onsubmit: onsubmit,
|
||||
prevent_default: "onsubmit", // Prevent the default behavior of <form> to post
|
||||
|
||||
input { "type": "text", id: "username", name: "username" }
|
||||
label { "Username" }
|
||||
br {}
|
||||
input { "type": "password", id: "password", name: "password" }
|
||||
label { "Password" }
|
||||
br {}
|
||||
button { "Login" }
|
||||
}
|
||||
})
|
||||
}
|
|
@ -35,8 +35,8 @@ fn app(cx: Scope) -> Element {
|
|||
div {
|
||||
h1 {"Dogs are very important"}
|
||||
p {
|
||||
"The dog or domestic dog (Canis familiaris[4][5] or Canis lupus familiaris[5])"
|
||||
"is a domesticated descendant of the wolf which is characterized by an upturning tail."
|
||||
"The dog or domestic dog (Canis familiaris[4][5] or Canis lupus familiaris[5])"
|
||||
"is a domesticated descendant of the wolf which is characterized by an upturning tail."
|
||||
"The dog derived from an ancient, extinct wolf,[6][7] and the modern grey wolf is the"
|
||||
"dog's nearest living relative.[8] The dog was the first species to be domesticated,[9][8]"
|
||||
"by hunter–gatherers over 15,000 years ago,[7] before the development of agriculture.[1]"
|
||||
|
@ -52,7 +52,7 @@ fn app(cx: Scope) -> Element {
|
|||
/// Suspense is achieved my moving the future into only the component that
|
||||
/// actually renders the data.
|
||||
fn Doggo(cx: Scope) -> Element {
|
||||
let fut = use_future(&cx, || async move {
|
||||
let fut = use_future(&cx, (), |_| async move {
|
||||
reqwest::get("https://dog.ceo/api/breeds/image/random/")
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
|
@ -12,8 +12,8 @@ fn main() {
|
|||
fn app(cx: Scope) -> Element {
|
||||
let (count, set_count) = use_state(&cx, || 0);
|
||||
|
||||
use_future(&cx, move || {
|
||||
let set_count = set_count.to_owned();
|
||||
use_future(&cx, (), move |_| {
|
||||
let set_count = set_count.clone();
|
||||
async move {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
|
|
23
examples/textarea.rs
Normal file
23
examples/textarea.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
// How to use textareas
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let (model, set_model) = use_state(&cx, || String::from("asd"));
|
||||
|
||||
println!("{}", model);
|
||||
|
||||
cx.render(rsx! {
|
||||
textarea {
|
||||
class: "border",
|
||||
rows: "10",
|
||||
cols: "80",
|
||||
value: "{model}",
|
||||
oninput: move |e| set_model(e.value.clone()),
|
||||
}
|
||||
})
|
||||
}
|
|
@ -129,7 +129,6 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
|
|||
label {
|
||||
r#for: "cbg-{todo.id}",
|
||||
onclick: move |_| set_is_editing(true),
|
||||
onfocusout: move |_| set_is_editing(false),
|
||||
"{todo.contents}"
|
||||
}
|
||||
}
|
||||
|
@ -139,6 +138,7 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
|
|||
value: "{todo.contents}",
|
||||
oninput: move |evt| cx.props.set_todos.make_mut()[&cx.props.id].contents = evt.value.clone(),
|
||||
autofocus: "true",
|
||||
onfocusout: move |_| set_is_editing(false),
|
||||
onkeydown: move |evt| {
|
||||
match evt.key.as_str() {
|
||||
"Enter" | "Escape" | "Tab" => set_is_editing(false),
|
||||
|
|
97
examples/window_event.rs
Normal file
97
examples/window_event.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch_cfg(app, |cfg| {
|
||||
cfg.with_window(|w| w.with_title("BorderLess Demo").with_decorations(false))
|
||||
});
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let window = dioxus::desktop::use_window(&cx);
|
||||
|
||||
// if you want to make window fullscreen, you need close the resizable.
|
||||
// window.set_fullscreen(true);
|
||||
// window.set_resizable(false);
|
||||
|
||||
let (fullscreen, set_fullscreen) = use_state(&cx, || false);
|
||||
let (always_on_top, set_always_on_top) = use_state(&cx, || false);
|
||||
let (decorations, set_decorations) = use_state(&cx, || false);
|
||||
|
||||
cx.render(rsx!(
|
||||
link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" }
|
||||
header {
|
||||
class: "text-gray-400 bg-gray-900 body-font",
|
||||
onmousedown: move |_| window.drag(),
|
||||
div {
|
||||
class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
|
||||
a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
|
||||
span { class: "ml-3 text-xl", "Dioxus"}
|
||||
}
|
||||
nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" }
|
||||
button {
|
||||
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
onmousedown: |evt| evt.cancel_bubble(),
|
||||
onclick: move |_| window.set_minimized(true),
|
||||
"Minimize"
|
||||
}
|
||||
button {
|
||||
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
onmousedown: |evt| evt.cancel_bubble(),
|
||||
onclick: move |_| {
|
||||
|
||||
window.set_fullscreen(!fullscreen);
|
||||
window.set_resizable(*fullscreen);
|
||||
|
||||
set_fullscreen(!fullscreen);
|
||||
},
|
||||
"Fullscreen"
|
||||
}
|
||||
button {
|
||||
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
onmousedown: |evt| evt.cancel_bubble(),
|
||||
onclick: move |_| window.close(),
|
||||
"Close"
|
||||
}
|
||||
}
|
||||
}
|
||||
br {}
|
||||
div {
|
||||
class: "container mx-auto",
|
||||
div {
|
||||
class: "grid grid-cols-5",
|
||||
div {
|
||||
button {
|
||||
class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
|
||||
onmousedown: |evt| evt.cancel_bubble(),
|
||||
onclick: move |_| {
|
||||
window.set_always_on_top(!always_on_top);
|
||||
set_always_on_top(!always_on_top);
|
||||
},
|
||||
"Always On Top"
|
||||
}
|
||||
}
|
||||
div {
|
||||
button {
|
||||
class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
|
||||
onmousedown: |evt| evt.cancel_bubble(),
|
||||
onclick: move |_| {
|
||||
window.set_decorations(!decorations);
|
||||
set_decorations(!decorations);
|
||||
},
|
||||
"Set Decorations"
|
||||
}
|
||||
}
|
||||
div {
|
||||
button {
|
||||
class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
|
||||
onmousedown: |evt| evt.cancel_bubble(),
|
||||
onclick: move |_| {
|
||||
window.set_title("Dioxus Application");
|
||||
},
|
||||
"Change Title"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
|
@ -44,7 +44,7 @@
|
|||
<h3>
|
||||
<a href="https://dioxuslabs.com"> 官网 </a>
|
||||
<span> | </span>
|
||||
<a href="https://dioxuslabs.com/guide"> 手册 </a>
|
||||
<a href="https://dioxus.mrxzx.info/"> 手册 </a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/DioxusLabs/example-projects"> 示例 </a>
|
||||
</h3>
|
||||
|
@ -65,12 +65,12 @@ Dioxus 是一个可移植、高性能的框架,用于在 Rust 中构建跨平
|
|||
|
||||
```rust
|
||||
fn app(cx: Scope) -> Element {
|
||||
let mut count = use_state(&cx, || 0);
|
||||
let (count, set_count) = use_state(&cx, || 0);
|
||||
|
||||
cx.render(rsx!(
|
||||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
button { onclick: move |_| set_count(count + 1), "Up high!" }
|
||||
button { onclick: move |_| set_count(count - 1), "Down low!" }
|
||||
))
|
||||
}
|
||||
```
|
||||
|
@ -160,6 +160,15 @@ Dioxus 使 Rust 应用程序的编写速度和 React 应用程序一样快,但
|
|||
- 您希望应用运行在 `不支持 Wasm 或 asm.js` 的浏览器。
|
||||
- 您需要一个 `Send + Sync` UI 解决方案(目前不支持)。
|
||||
|
||||
### 项目生态
|
||||
|
||||
想要加入我们一起为 Dioxus 生态努力吗?有很多项目都能在您的帮助下获得改变:
|
||||
|
||||
- [TUI 渲染器](https://github.com/dioxusLabs/rink)
|
||||
- [CLI 开发工具](https://github.com/dioxusLabs/cli)
|
||||
- [官网及文档](https://github.com/dioxusLabs/docsite)
|
||||
- 动态网站 及 Web 服务器
|
||||
- 资源系统
|
||||
|
||||
## 协议
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
once_cell = "1.8"
|
||||
proc-macro-error = "1.0.4"
|
||||
# dioxus-rsx = { path = "../rsx" }
|
||||
proc-macro-error = "1"
|
||||
proc-macro2 = { version = "1.0.6" }
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
fn main() {
|
||||
// use dioxus_core_macro::*;
|
||||
// let r = html! {
|
||||
// <div>
|
||||
// "hello world"
|
||||
// </div>
|
||||
// };
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
use dioxus_core_macro::{inline_props, Props};
|
||||
|
||||
fn main() {}
|
||||
|
||||
type Element<'a> = ();
|
||||
|
||||
pub struct Scope<'a, T = ()> {
|
||||
props: &'a T,
|
||||
}
|
||||
|
||||
// #[inline_props]
|
||||
pub fn component<'a>(
|
||||
cx: Scope<'a>,
|
||||
chkk: String,
|
||||
chkk2: String,
|
||||
r: u32,
|
||||
cat: &'a str,
|
||||
drd: String,
|
||||
e: String,
|
||||
) -> Element<'a> {
|
||||
let r = chkk.len();
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
fn main() {}
|
||||
|
||||
pub mod dioxus {
|
||||
pub mod prelude {
|
||||
pub trait Properties {
|
||||
type Builder;
|
||||
const IS_STATIC: bool;
|
||||
fn builder() -> Self::Builder;
|
||||
unsafe fn memoize(&self, other: &Self) -> bool;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This implementation should require a "PartialEq" because it memoizes (no external references)
|
||||
#[derive(PartialEq, dioxus_core_macro::Props)]
|
||||
struct SomeProps {
|
||||
a: String,
|
||||
}
|
||||
|
||||
/// This implementation does not require a "PartialEq" because it does not memoize
|
||||
#[derive(dioxus_core_macro::Props)]
|
||||
struct SomePropsTwo<'a> {
|
||||
_a: &'a str,
|
||||
}
|
|
@ -1,443 +0,0 @@
|
|||
//!
|
||||
//! TODO:
|
||||
//! - [ ] Support for VComponents
|
||||
//! - [ ] Support for inline format in text
|
||||
//! - [ ] Support for expressions in attribute positions
|
||||
//! - [ ] Support for iterators
|
||||
//! - [ ] support for inline html!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
|
||||
use {
|
||||
proc_macro2::TokenStream as TokenStream2,
|
||||
quote::{quote, ToTokens, TokenStreamExt},
|
||||
syn::{
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseStream},
|
||||
token, Error, Expr, ExprClosure, Ident, LitStr, Result, Token,
|
||||
},
|
||||
};
|
||||
|
||||
// ==============================================
|
||||
// Parse any stream coming from the html! macro
|
||||
// ==============================================
|
||||
pub struct HtmlRender {
|
||||
kind: NodeOrList,
|
||||
}
|
||||
|
||||
impl Parse for HtmlRender {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
if input.peek(LitStr) {
|
||||
return input.parse::<LitStr>()?.parse::<HtmlRender>();
|
||||
}
|
||||
|
||||
// let __cx: Ident = s.parse()?;
|
||||
// s.parse::<Token![,]>()?;
|
||||
// if elements are in an array, return a bumpalo::collections::Vec rather than a Node.
|
||||
let kind = if input.peek(token::Bracket) {
|
||||
let nodes_toks;
|
||||
syn::bracketed!(nodes_toks in input);
|
||||
let mut nodes: Vec<MaybeExpr<Node>> = vec![nodes_toks.parse()?];
|
||||
while nodes_toks.peek(Token![,]) {
|
||||
nodes_toks.parse::<Token![,]>()?;
|
||||
nodes.push(nodes_toks.parse()?);
|
||||
}
|
||||
NodeOrList::List(NodeList(nodes))
|
||||
} else {
|
||||
NodeOrList::Node(input.parse()?)
|
||||
};
|
||||
Ok(HtmlRender { kind })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for HtmlRender {
|
||||
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||
let new_toks = ToToksCtx::new(&self.kind).to_token_stream();
|
||||
|
||||
// create a lazy tree that accepts a bump allocator
|
||||
let final_tokens = quote! {
|
||||
dioxus::prelude::LazyNodes::new(move |__cx| {
|
||||
let bump = __cx.bump();
|
||||
|
||||
#new_toks
|
||||
})
|
||||
};
|
||||
|
||||
final_tokens.to_tokens(out_tokens);
|
||||
}
|
||||
}
|
||||
|
||||
/// =============================================
|
||||
/// Parse any child as a node or list of nodes
|
||||
/// =============================================
|
||||
/// - [ ] Allow iterators
|
||||
///
|
||||
///
|
||||
enum NodeOrList {
|
||||
Node(Node),
|
||||
List(NodeList),
|
||||
}
|
||||
|
||||
impl ToTokens for ToToksCtx<&NodeOrList> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match self.inner {
|
||||
NodeOrList::Node(node) => self.recurse(node).to_tokens(tokens),
|
||||
NodeOrList::List(list) => self.recurse(list).to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeList(Vec<MaybeExpr<Node>>);
|
||||
|
||||
impl ToTokens for ToToksCtx<&NodeList> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let nodes = self.inner.0.iter().map(|node| self.recurse(node));
|
||||
tokens.append_all(quote! {
|
||||
dioxus::bumpalo::vec![in bump;
|
||||
#(#nodes),*
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum Node {
|
||||
Element(Element),
|
||||
Text(TextNode),
|
||||
}
|
||||
|
||||
impl ToTokens for ToToksCtx<&Node> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match &self.inner {
|
||||
Node::Element(el) => self.recurse(el).to_tokens(tokens),
|
||||
Node::Text(txt) => self.recurse(txt).to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn _peek(s: ParseStream) -> bool {
|
||||
(s.peek(Token![<]) && !s.peek2(Token![/])) || s.peek(token::Brace) || s.peek(LitStr)
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Node {
|
||||
fn parse(s: ParseStream) -> Result<Self> {
|
||||
Ok(if s.peek(Token![<]) {
|
||||
Node::Element(s.parse()?)
|
||||
} else {
|
||||
Node::Text(s.parse()?)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// =======================================
|
||||
/// Parse the VNode::Element type
|
||||
/// =======================================
|
||||
/// - [ ] Allow VComponent
|
||||
///
|
||||
///
|
||||
struct Element {
|
||||
name: Ident,
|
||||
attrs: Vec<Attr>,
|
||||
children: MaybeExpr<Vec<Node>>,
|
||||
}
|
||||
|
||||
impl ToTokens for ToToksCtx<&Element> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
// let __cx = self.__cx;
|
||||
let name = &self.inner.name;
|
||||
// let name = &self.inner.name.to_string();
|
||||
tokens.append_all(quote! {
|
||||
__cx.element(dioxus_elements::#name)
|
||||
// dioxus::builder::ElementBuilder::new( #name)
|
||||
});
|
||||
for attr in self.inner.attrs.iter() {
|
||||
self.recurse(attr).to_tokens(tokens);
|
||||
}
|
||||
|
||||
// if is_valid_svg_tag(&name.to_string()) {
|
||||
// tokens.append_all(quote! {
|
||||
// .namespace(Some("http://www.w3.org/2000/svg"))
|
||||
// });
|
||||
// }
|
||||
|
||||
match &self.inner.children {
|
||||
MaybeExpr::Expr(expr) => tokens.append_all(quote! {
|
||||
.children(#expr)
|
||||
}),
|
||||
MaybeExpr::Literal(nodes) => {
|
||||
let mut children = nodes.iter();
|
||||
if let Some(child) = children.next() {
|
||||
let mut inner_toks = TokenStream2::new();
|
||||
self.recurse(child).to_tokens(&mut inner_toks);
|
||||
for child in children {
|
||||
quote!(,).to_tokens(&mut inner_toks);
|
||||
self.recurse(child).to_tokens(&mut inner_toks);
|
||||
}
|
||||
tokens.append_all(quote! {
|
||||
.children([#inner_toks])
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens.append_all(quote! {
|
||||
.finish()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Element {
|
||||
fn parse(s: ParseStream) -> Result<Self> {
|
||||
s.parse::<Token![<]>()?;
|
||||
let name = Ident::parse_any(s)?;
|
||||
let mut attrs = vec![];
|
||||
let _children: Vec<Node> = vec![];
|
||||
|
||||
// keep looking for attributes
|
||||
while !s.peek(Token![>]) {
|
||||
// self-closing
|
||||
if s.peek(Token![/]) {
|
||||
s.parse::<Token![/]>()?;
|
||||
s.parse::<Token![>]>()?;
|
||||
return Ok(Self {
|
||||
name,
|
||||
attrs,
|
||||
children: MaybeExpr::Literal(vec![]),
|
||||
});
|
||||
}
|
||||
attrs.push(s.parse()?);
|
||||
}
|
||||
s.parse::<Token![>]>()?;
|
||||
|
||||
// Contents of an element can either be a brace (in which case we just copy verbatim), or a
|
||||
// sequence of nodes.
|
||||
let children = if s.peek(token::Brace) {
|
||||
// expr
|
||||
let content;
|
||||
syn::braced!(content in s);
|
||||
MaybeExpr::Expr(content.parse()?)
|
||||
} else {
|
||||
// nodes
|
||||
let mut children = vec![];
|
||||
while !(s.peek(Token![<]) && s.peek2(Token![/])) {
|
||||
children.push(s.parse()?);
|
||||
}
|
||||
MaybeExpr::Literal(children)
|
||||
};
|
||||
|
||||
// closing element
|
||||
s.parse::<Token![<]>()?;
|
||||
s.parse::<Token![/]>()?;
|
||||
let close = Ident::parse_any(s)?;
|
||||
if close != name {
|
||||
return Err(Error::new_spanned(
|
||||
close,
|
||||
"closing element does not match opening",
|
||||
));
|
||||
}
|
||||
s.parse::<Token![>]>()?;
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
attrs,
|
||||
children,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// =======================================
|
||||
/// Parse a VElement's Attributes
|
||||
/// =======================================
|
||||
/// - [ ] Allow expressions as attribute
|
||||
///
|
||||
///
|
||||
struct Attr {
|
||||
name: Ident,
|
||||
ty: AttrType,
|
||||
}
|
||||
|
||||
impl Parse for Attr {
|
||||
fn parse(s: ParseStream) -> Result<Self> {
|
||||
let mut name = Ident::parse_any(s)?;
|
||||
let name_str = name.to_string();
|
||||
s.parse::<Token![=]>()?;
|
||||
|
||||
// Check if this is an event handler
|
||||
// If so, parse into literal tokens
|
||||
let ty = if name_str.starts_with("on") {
|
||||
// remove the "on" bit
|
||||
name = Ident::new(name_str.trim_start_matches("on"), name.span());
|
||||
let content;
|
||||
syn::braced!(content in s);
|
||||
// AttrType::Value(content.parse()?)
|
||||
AttrType::Event(content.parse()?)
|
||||
// AttrType::Event(content.parse()?)
|
||||
} else {
|
||||
let lit_str = if name_str == "style" && s.peek(token::Brace) {
|
||||
// special-case to deal with literal styles.
|
||||
let outer;
|
||||
syn::braced!(outer in s);
|
||||
// double brace for inline style.
|
||||
// todo!("Style support not ready yet");
|
||||
|
||||
// if outer.peek(token::Brace) {
|
||||
// let inner;
|
||||
// syn::braced!(inner in outer);
|
||||
// let styles: Styles = inner.parse()?;
|
||||
// MaybeExpr::Literal(LitStr::new(&styles.to_string(), Span::call_site()))
|
||||
// } else {
|
||||
// just parse as an expression
|
||||
MaybeExpr::Expr(outer.parse()?)
|
||||
// }
|
||||
} else {
|
||||
s.parse()?
|
||||
};
|
||||
AttrType::Value(lit_str)
|
||||
};
|
||||
Ok(Attr { name, ty })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ToToksCtx<&Attr> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let name = self.inner.name.to_string();
|
||||
let _attr_stream = TokenStream2::new();
|
||||
match &self.inner.ty {
|
||||
AttrType::Value(value) => {
|
||||
let value = self.recurse(value);
|
||||
if name == "xmlns" {
|
||||
tokens.append_all(quote! {
|
||||
.namespace(Some(#value))
|
||||
});
|
||||
} else {
|
||||
tokens.append_all(quote! {
|
||||
.attr(#name, format_args_f!(#value))
|
||||
});
|
||||
}
|
||||
}
|
||||
AttrType::Event(event) => {
|
||||
tokens.append_all(quote! {
|
||||
.on(#name, #event)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AttrType {
|
||||
Value(MaybeExpr<LitStr>),
|
||||
Event(ExprClosure),
|
||||
// todo Bool(MaybeExpr<LitBool>)
|
||||
}
|
||||
|
||||
/// =======================================
|
||||
/// Parse just plain text
|
||||
/// =======================================
|
||||
/// - [ ] Perform formatting automatically
|
||||
///
|
||||
///
|
||||
struct TextNode(MaybeExpr<LitStr>);
|
||||
|
||||
impl Parse for TextNode {
|
||||
fn parse(s: ParseStream) -> Result<Self> {
|
||||
Ok(Self(s.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ToToksCtx<&TextNode> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let mut token_stream = TokenStream2::new();
|
||||
self.recurse(&self.inner.0).to_tokens(&mut token_stream);
|
||||
tokens.append_all(quote! {
|
||||
__cx.text(format_args_f!(#token_stream))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum MaybeExpr<T> {
|
||||
Literal(T),
|
||||
Expr(Expr),
|
||||
}
|
||||
|
||||
impl<T: Parse> Parse for MaybeExpr<T> {
|
||||
fn parse(s: ParseStream) -> Result<Self> {
|
||||
if s.peek(token::Brace) {
|
||||
let content;
|
||||
syn::braced!(content in s);
|
||||
Ok(MaybeExpr::Expr(content.parse()?))
|
||||
} else {
|
||||
Ok(MaybeExpr::Literal(s.parse()?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> ToTokens for ToToksCtx<&'a MaybeExpr<T>>
|
||||
where
|
||||
T: 'a,
|
||||
ToToksCtx<&'a T>: ToTokens,
|
||||
{
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match &self.inner {
|
||||
MaybeExpr::Literal(v) => self.recurse(v).to_tokens(tokens),
|
||||
MaybeExpr::Expr(expr) => expr.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ToTokens context
|
||||
struct ToToksCtx<T> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<'a, T> ToToksCtx<T> {
|
||||
fn new(inner: T) -> Self {
|
||||
ToToksCtx { inner }
|
||||
}
|
||||
|
||||
fn recurse<U>(&self, inner: U) -> ToToksCtx<U> {
|
||||
ToToksCtx { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ToToksCtx<&LitStr> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
self.inner.to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
fn parse(input: &str) -> super::Result<super::HtmlRender> {
|
||||
syn::parse_str(input)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div() {
|
||||
parse("bump, <div class=\"test\"/>").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested() {
|
||||
parse("bump, <div class=\"test\"><div />\"text\"</div>").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex() {
|
||||
parse(
|
||||
"bump,
|
||||
<section style={{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 95%;
|
||||
}} class=\"map-panel\">{contact_details}</section>
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
|
@ -2,11 +2,10 @@ use proc_macro::TokenStream;
|
|||
use quote::ToTokens;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
pub(crate) mod ifmt;
|
||||
pub(crate) mod inlineprops;
|
||||
pub(crate) mod props;
|
||||
pub(crate) mod router;
|
||||
pub(crate) mod rsx;
|
||||
mod ifmt;
|
||||
mod inlineprops;
|
||||
mod props;
|
||||
mod rsx;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn format_args_f(input: TokenStream) -> TokenStream {
|
||||
|
@ -186,36 +185,6 @@ pub fn rsx(s: TokenStream) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
/// Derive macro used to mark an enum as Routable.
|
||||
///
|
||||
/// This macro can only be used on enums. Every varient of the macro needs to be marked
|
||||
/// with the `at` attribute to specify the URL of the route. It generates an implementation of
|
||||
/// `yew_router::Routable` trait and `const`s for the routes passed which are used with `Route`
|
||||
/// component.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use yew_router::Routable;
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Routable)]
|
||||
/// enum Routes {
|
||||
/// #[at("/")]
|
||||
/// Home,
|
||||
/// #[at("/secure")]
|
||||
/// Secure,
|
||||
/// #[at("/profile/{id}")]
|
||||
/// Profile(u32),
|
||||
/// #[at("/404")]
|
||||
/// NotFound,
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(Routable, attributes(at, not_found))]
|
||||
pub fn routable_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
use router::{routable_derive_impl, Routable};
|
||||
let input = parse_macro_input!(input as Routable);
|
||||
routable_derive_impl(input).into()
|
||||
}
|
||||
|
||||
/// Derive props for a component within the component definition.
|
||||
///
|
||||
/// This macro provides a simple transformation from `Scope<{}>` to `Scope<P>`,
|
||||
|
|
|
@ -1,214 +0,0 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Data, DeriveInput, Fields, Ident, LitStr, Variant};
|
||||
|
||||
const AT_ATTR_IDENT: &str = "at";
|
||||
const NOT_FOUND_ATTR_IDENT: &str = "not_found";
|
||||
|
||||
pub struct Routable {
|
||||
ident: Ident,
|
||||
ats: Vec<LitStr>,
|
||||
variants: Punctuated<Variant, syn::token::Comma>,
|
||||
not_found_route: Option<Ident>,
|
||||
}
|
||||
|
||||
impl Parse for Routable {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let DeriveInput { ident, data, .. } = input.parse()?;
|
||||
|
||||
let data = match data {
|
||||
Data::Enum(data) => data,
|
||||
Data::Struct(s) => {
|
||||
return Err(syn::Error::new(
|
||||
s.struct_token.span(),
|
||||
"expected enum, found struct",
|
||||
))
|
||||
}
|
||||
Data::Union(u) => {
|
||||
return Err(syn::Error::new(
|
||||
u.union_token.span(),
|
||||
"expected enum, found union",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let (not_found_route, ats) = parse_variants_attributes(&data.variants)?;
|
||||
|
||||
Ok(Self {
|
||||
ident,
|
||||
variants: data.variants,
|
||||
ats,
|
||||
not_found_route,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_variants_attributes(
|
||||
variants: &Punctuated<Variant, syn::token::Comma>,
|
||||
) -> syn::Result<(Option<Ident>, Vec<LitStr>)> {
|
||||
let mut not_founds = vec![];
|
||||
let mut ats: Vec<LitStr> = vec![];
|
||||
|
||||
let mut not_found_attrs = vec![];
|
||||
|
||||
for variant in variants.iter() {
|
||||
if let Fields::Unnamed(ref field) = variant.fields {
|
||||
return Err(syn::Error::new(
|
||||
field.span(),
|
||||
"only named fields are supported",
|
||||
));
|
||||
}
|
||||
|
||||
let attrs = &variant.attrs;
|
||||
let at_attrs = attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path.is_ident(AT_ATTR_IDENT))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let attr = match at_attrs.len() {
|
||||
1 => *at_attrs.first().unwrap(),
|
||||
0 => {
|
||||
return Err(syn::Error::new(
|
||||
variant.span(),
|
||||
format!(
|
||||
"{} attribute must be present on every variant",
|
||||
AT_ATTR_IDENT
|
||||
),
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
quote! { #(#at_attrs)* },
|
||||
format!("only one {} attribute must be present", AT_ATTR_IDENT),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let lit = attr.parse_args::<LitStr>()?;
|
||||
ats.push(lit);
|
||||
|
||||
for attr in attrs.iter() {
|
||||
if attr.path.is_ident(NOT_FOUND_ATTR_IDENT) {
|
||||
not_found_attrs.push(attr);
|
||||
not_founds.push(variant.ident.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if not_founds.len() > 1 {
|
||||
return Err(syn::Error::new_spanned(
|
||||
quote! { #(#not_found_attrs)* },
|
||||
format!("there can only be one {}", NOT_FOUND_ATTR_IDENT),
|
||||
));
|
||||
}
|
||||
|
||||
Ok((not_founds.into_iter().next(), ats))
|
||||
}
|
||||
|
||||
impl Routable {
|
||||
// fn build_from_path(&self) -> TokenStream {
|
||||
// let from_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
|
||||
// let ident = &variant.ident;
|
||||
// let right = match &variant.fields {
|
||||
// Fields::Unit => quote! { Self::#ident },
|
||||
// Fields::Named(field) => {
|
||||
// let fields = field.named.iter().map(|it| {
|
||||
// //named fields have idents
|
||||
// it.ident.as_ref().unwrap()
|
||||
// });
|
||||
// quote! { Self::#ident { #(#fields: params.get(stringify!(#fields))?.parse().ok()?,)* } }
|
||||
// }
|
||||
// Fields::Unnamed(_) => unreachable!(), // already checked
|
||||
// };
|
||||
|
||||
// let left = self.ats.get(i).unwrap();
|
||||
// quote! {
|
||||
// #left => ::std::option::Option::Some(#right)
|
||||
// }
|
||||
// });
|
||||
|
||||
// quote! {
|
||||
// fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option<Self> {
|
||||
// match path {
|
||||
// #(#from_path_matches),*,
|
||||
// _ => ::std::option::Option::None,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn build_to_path(&self) -> TokenStream {
|
||||
// let to_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
|
||||
// let ident = &variant.ident;
|
||||
// let mut right = self.ats.get(i).unwrap().value();
|
||||
|
||||
// match &variant.fields {
|
||||
// Fields::Unit => quote! { Self::#ident => ::std::string::ToString::to_string(#right) },
|
||||
// Fields::Named(field) => {
|
||||
// let fields = field
|
||||
// .named
|
||||
// .iter()
|
||||
// .map(|it| it.ident.as_ref().unwrap())
|
||||
// .collect::<Vec<_>>();
|
||||
|
||||
// for field in fields.iter() {
|
||||
// // :param -> {param}
|
||||
// // so we can pass it to `format!("...", param)`
|
||||
// right = right.replace(&format!(":{}", field), &format!("{{{}}}", field))
|
||||
// }
|
||||
|
||||
// quote! {
|
||||
// Self::#ident { #(#fields),* } => ::std::format!(#right, #(#fields = #fields),*)
|
||||
// }
|
||||
// }
|
||||
// Fields::Unnamed(_) => unreachable!(), // already checked
|
||||
// }
|
||||
// });
|
||||
|
||||
// quote! {
|
||||
// fn to_path(&self) -> ::std::string::String {
|
||||
// match self {
|
||||
// #(#to_path_matches),*,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn routable_derive_impl(input: Routable) -> TokenStream {
|
||||
let Routable {
|
||||
// ats,
|
||||
// not_found_route,
|
||||
// ident,
|
||||
..
|
||||
} = &input;
|
||||
|
||||
// let from_path = input.build_from_path();
|
||||
// let to_path = input.build_to_path();
|
||||
|
||||
quote! {
|
||||
// #[automatically_derived]
|
||||
// impl ::dioxus::router::Routable for #ident {
|
||||
|
||||
// fn recognize(pathname: &str) -> ::std::option::Option<Self> {
|
||||
// todo!()
|
||||
// // ::std::thread_local! {
|
||||
// // static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>();
|
||||
// // }
|
||||
// // let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname));
|
||||
// // {
|
||||
// // let route = ::std::clone::Clone::clone(&route);
|
||||
// // #cache_thread_local_ident.with(move |val| {
|
||||
// // *val.borrow_mut() = route;
|
||||
// // });
|
||||
// // }
|
||||
// // route
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -23,10 +23,10 @@ use syn::{
|
|||
};
|
||||
|
||||
pub struct Component {
|
||||
name: syn::Path,
|
||||
body: Vec<ComponentField>,
|
||||
children: Vec<BodyNode>,
|
||||
manual_props: Option<Expr>,
|
||||
pub name: syn::Path,
|
||||
pub body: Vec<ComponentField>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub manual_props: Option<Expr>,
|
||||
}
|
||||
|
||||
impl Parse for Component {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue