mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
Merge branch 'master' into feature/use-shared-state-better-diagnostics
This commit is contained in:
commit
91d4207fa7
324 changed files with 13509 additions and 6646 deletions
6
.github/workflows/docs stable.yml
vendored
6
.github/workflows/docs stable.yml
vendored
|
@ -3,6 +3,10 @@ name: docs stable
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-deploy:
|
build-deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -29,7 +33,7 @@ jobs:
|
||||||
# cd fermi && mdbook build -d ../nightly/fermi && cd ..
|
# cd fermi && mdbook build -d ../nightly/fermi && cd ..
|
||||||
|
|
||||||
- name: Deploy 🚀
|
- name: Deploy 🚀
|
||||||
uses: JamesIves/github-pages-deploy-action@v4.4.2
|
uses: JamesIves/github-pages-deploy-action@v4.4.3
|
||||||
with:
|
with:
|
||||||
branch: gh-pages # The branch the action should deploy to.
|
branch: gh-pages # The branch the action should deploy to.
|
||||||
folder: docs/nightly # The folder the action should deploy.
|
folder: docs/nightly # The folder the action should deploy.
|
||||||
|
|
6
.github/workflows/docs.yml
vendored
6
.github/workflows/docs.yml
vendored
|
@ -8,6 +8,10 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-deploy:
|
build-deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -34,7 +38,7 @@ jobs:
|
||||||
# cd fermi && mdbook build -d ../nightly/fermi && cd ..
|
# cd fermi && mdbook build -d ../nightly/fermi && cd ..
|
||||||
|
|
||||||
- name: Deploy 🚀
|
- name: Deploy 🚀
|
||||||
uses: JamesIves/github-pages-deploy-action@v4.4.2
|
uses: JamesIves/github-pages-deploy-action@v4.4.3
|
||||||
with:
|
with:
|
||||||
branch: gh-pages # The branch the action should deploy to.
|
branch: gh-pages # The branch the action should deploy to.
|
||||||
folder: docs/nightly # The folder the action should deploy.
|
folder: docs/nightly # The folder the action should deploy.
|
||||||
|
|
36
.github/workflows/macos.yml
vendored
36
.github/workflows/macos.yml
vendored
|
@ -1,36 +0,0 @@
|
||||||
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:
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- run: cargo test --all --tests
|
|
70
.github/workflows/main.yml
vendored
70
.github/workflows/main.yml
vendored
|
@ -13,7 +13,7 @@ on:
|
||||||
- lib.rs
|
- lib.rs
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
- Makefile.toml
|
- Makefile.toml
|
||||||
- playwrite-tests/**
|
- playwright-tests/**
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened, ready_for_review]
|
types: [opened, synchronize, reopened, ready_for_review]
|
||||||
|
@ -27,6 +27,10 @@ on:
|
||||||
- lib.rs
|
- lib.rs
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
if: github.event.pull_request.draft == false
|
if: github.event.pull_request.draft == false
|
||||||
|
@ -79,6 +83,69 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: cargo clippy --workspace --examples --tests -- -D warnings
|
- run: cargo clippy --workspace --examples --tests -- -D warnings
|
||||||
|
|
||||||
|
matrix_test:
|
||||||
|
runs-on: ${{ matrix.platform.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- {
|
||||||
|
target: x86_64-pc-windows-msvc,
|
||||||
|
os: windows-latest,
|
||||||
|
toolchain: '1.70.0',
|
||||||
|
cross: false,
|
||||||
|
command: 'test',
|
||||||
|
args: '--all --tests'
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
target: x86_64-apple-darwin,
|
||||||
|
os: macos-latest,
|
||||||
|
toolchain: '1.70.0',
|
||||||
|
cross: false,
|
||||||
|
command: 'test',
|
||||||
|
args: '--all --tests'
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
target: aarch64-apple-ios,
|
||||||
|
os: macos-latest,
|
||||||
|
toolchain: '1.70.0',
|
||||||
|
cross: false,
|
||||||
|
command: 'build',
|
||||||
|
args: '--package dioxus-mobile'
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
target: aarch64-linux-android,
|
||||||
|
os: ubuntu-latest,
|
||||||
|
toolchain: '1.70.0',
|
||||||
|
cross: true,
|
||||||
|
command: 'build',
|
||||||
|
args: '--package dioxus-mobile'
|
||||||
|
}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: install stable
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.platform.toolchain }}
|
||||||
|
target: ${{ matrix.platform.target }}
|
||||||
|
override: true
|
||||||
|
default: true
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: core -> ../target
|
||||||
|
save-if: ${{ matrix.features.key == 'all' }}
|
||||||
|
|
||||||
|
- name: test
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: ${{ matrix.platform.cross }}
|
||||||
|
command: ${{ matrix.platform.command }}
|
||||||
|
args: --target ${{ matrix.platform.target }} ${{ matrix.platform.args }}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Coverage is disabled until we can fix it
|
# Coverage is disabled until we can fix it
|
||||||
# coverage:
|
# coverage:
|
||||||
# name: Coverage
|
# name: Coverage
|
||||||
|
@ -99,3 +166,4 @@ jobs:
|
||||||
# uses: codecov/codecov-action@v2
|
# uses: codecov/codecov-action@v2
|
||||||
# with:
|
# with:
|
||||||
# fail_ci_if_error: false
|
# fail_ci_if_error: false
|
||||||
|
|
||||||
|
|
13
.github/workflows/miri.yml
vendored
13
.github/workflows/miri.yml
vendored
|
@ -6,6 +6,13 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- 'auto'
|
- 'auto'
|
||||||
- 'try'
|
- 'try'
|
||||||
|
paths:
|
||||||
|
- packages/**
|
||||||
|
- examples/**
|
||||||
|
- src/**
|
||||||
|
- .github/**
|
||||||
|
- lib.rs
|
||||||
|
- Cargo.toml
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened, ready_for_review]
|
types: [opened, synchronize, reopened, ready_for_review]
|
||||||
branches:
|
branches:
|
||||||
|
@ -31,7 +38,9 @@ env:
|
||||||
# - tokio-stream/Cargo.toml
|
# - tokio-stream/Cargo.toml
|
||||||
# rust_min: 1.49.0
|
# rust_min: 1.49.0
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
@ -72,7 +81,7 @@ jobs:
|
||||||
# #[tokio::main] that calls epoll_create1 that Miri does not support.
|
# #[tokio::main] that calls epoll_create1 that Miri does not support.
|
||||||
# run: cargo miri test --features full --lib --no-fail-fast
|
# run: cargo miri test --features full --lib --no-fail-fast
|
||||||
run: |
|
run: |
|
||||||
cargo miri test --package dioxus-core --test miri_stress -- --exact --nocapture
|
cargo miri test --package dioxus-core -- --exact --nocapture
|
||||||
cargo miri test --package dioxus-native-core --test miri_native -- --exact --nocapture
|
cargo miri test --package dioxus-native-core --test miri_native -- --exact --nocapture
|
||||||
|
|
||||||
# working-directory: tokio
|
# working-directory: tokio
|
||||||
|
|
32
.github/workflows/playwright.yml
vendored
32
.github/workflows/playwright.yml
vendored
|
@ -4,22 +4,25 @@ on:
|
||||||
branches: [ main, master ]
|
branches: [ main, master ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main, master ]
|
branches: [ main, master ]
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./playwright-tests
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
if: github.event.pull_request.draft == false
|
if: github.event.pull_request.draft == false
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
|
# Do our best to cache the toolchain and node install steps
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
- name: Install Playwright
|
|
||||||
run: npm install -D @playwright/test
|
|
||||||
- name: Install Playwright Browsers
|
|
||||||
run: npx playwright install --with-deps
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
|
@ -29,11 +32,18 @@ jobs:
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: Install WASM toolchain
|
- name: Install WASM toolchain
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
- name: Install Dioxus CLI
|
- name: Install dependencies
|
||||||
uses: actions-rs/cargo@v1
|
run: npm ci
|
||||||
with:
|
- name: Install Playwright
|
||||||
command: install
|
run: npm install -D @playwright/test
|
||||||
args: --path packages/cli
|
- name: Install Playwright Browsers
|
||||||
|
run: npx playwright install --with-deps
|
||||||
|
# Cache the CLI by using cargo run internally
|
||||||
|
# - name: Install Dioxus CLI
|
||||||
|
# uses: actions-rs/cargo@v1
|
||||||
|
# with:
|
||||||
|
# command: install
|
||||||
|
# args: --path packages/cli
|
||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
run: npx playwright test
|
run: npx playwright test
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
|
|
88
.github/workflows/windows.yml
vendored
88
.github/workflows/windows.yml
vendored
|
@ -1,88 +0,0 @@
|
||||||
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:
|
|
||||||
CFG_RELEASE_CHANNEL: ${{ matrix.cfg_release_channel }}
|
|
||||||
strategy:
|
|
||||||
# https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits
|
|
||||||
# There's a limit of 60 concurrent jobs across all repos in the rust-lang organization.
|
|
||||||
# In order to prevent overusing too much of that 60 limit, we throttle the
|
|
||||||
# number of rustfmt jobs that will run concurrently.
|
|
||||||
# max-parallel:
|
|
||||||
# fail-fast: false
|
|
||||||
matrix:
|
|
||||||
target: [x86_64-pc-windows-gnu, x86_64-pc-windows-msvc]
|
|
||||||
cfg_release_channel: [stable]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# The Windows runners have autocrlf enabled by default
|
|
||||||
# which causes failures for some of rustfmt's line-ending sensitive tests
|
|
||||||
- name: disable git eol translation
|
|
||||||
run: git config --global core.autocrlf false
|
|
||||||
|
|
||||||
# Run build
|
|
||||||
- name: Install Rustup using win.rustup.rs
|
|
||||||
run: |
|
|
||||||
# Disable the download progress bar which can cause perf issues
|
|
||||||
$ProgressPreference = "SilentlyContinue"
|
|
||||||
Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe
|
|
||||||
.\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --default-toolchain=none
|
|
||||||
del rustup-init.exe
|
|
||||||
rustup target add ${{ matrix.target }}
|
|
||||||
shell: powershell
|
|
||||||
|
|
||||||
- name: Add mingw64 to path for x86_64-gnu
|
|
||||||
run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH
|
|
||||||
if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly'
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
# - name: checkout
|
|
||||||
# uses: actions/checkout@v3
|
|
||||||
# with:
|
|
||||||
# path: C:/dioxus.git
|
|
||||||
# fetch-depth: 1
|
|
||||||
|
|
||||||
# we need to use the C drive as the working directory
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
run: |
|
|
||||||
mkdir C:/dioxus.git
|
|
||||||
git clone https://github.com/dioxuslabs/dioxus.git C:/dioxus.git --depth 1
|
|
||||||
|
|
||||||
- name: test
|
|
||||||
working-directory: C:/dioxus.git
|
|
||||||
run: |
|
|
||||||
rustc -Vv
|
|
||||||
cargo -V
|
|
||||||
set RUST_BACKTRACE=1
|
|
||||||
cargo build --all --tests --examples
|
|
||||||
cargo test --all --tests
|
|
||||||
shell: cmd
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,6 +1,6 @@
|
||||||
/target
|
/target
|
||||||
/playwrite-tests/web/dist
|
/playwright-tests/web/dist
|
||||||
/playwrite-tests/fullstack/dist
|
/playwright-tests/fullstack/dist
|
||||||
/dist
|
/dist
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -4,6 +4,7 @@ members = [
|
||||||
"packages/core",
|
"packages/core",
|
||||||
"packages/cli",
|
"packages/cli",
|
||||||
"packages/core-macro",
|
"packages/core-macro",
|
||||||
|
"packages/router-macro",
|
||||||
"packages/extension",
|
"packages/extension",
|
||||||
"packages/router",
|
"packages/router",
|
||||||
"packages/html",
|
"packages/html",
|
||||||
|
@ -29,16 +30,19 @@ members = [
|
||||||
"packages/fullstack/examples/axum-hello-world",
|
"packages/fullstack/examples/axum-hello-world",
|
||||||
"packages/fullstack/examples/axum-router",
|
"packages/fullstack/examples/axum-router",
|
||||||
"packages/fullstack/examples/axum-desktop",
|
"packages/fullstack/examples/axum-desktop",
|
||||||
|
"packages/fullstack/examples/axum-auth",
|
||||||
"packages/fullstack/examples/salvo-hello-world",
|
"packages/fullstack/examples/salvo-hello-world",
|
||||||
"packages/fullstack/examples/warp-hello-world",
|
"packages/fullstack/examples/warp-hello-world",
|
||||||
|
"packages/fullstack/examples/static-hydrated",
|
||||||
"docs/guide",
|
"docs/guide",
|
||||||
|
"docs/router",
|
||||||
# Full project examples
|
# Full project examples
|
||||||
"examples/tailwind",
|
"examples/tailwind",
|
||||||
"examples/PWA-example",
|
"examples/PWA-example",
|
||||||
# Playwrite tests
|
# Playwright tests
|
||||||
"playwrite-tests/liveview",
|
"playwright-tests/liveview",
|
||||||
"playwrite-tests/web",
|
"playwright-tests/web",
|
||||||
"playwrite-tests/fullstack",
|
"playwright-tests/fullstack",
|
||||||
]
|
]
|
||||||
exclude = ["examples/mobile_demo"]
|
exclude = ["examples/mobile_demo"]
|
||||||
|
|
||||||
|
@ -48,6 +52,7 @@ dioxus = { path = "packages/dioxus" }
|
||||||
dioxus-core = { path = "packages/core" }
|
dioxus-core = { path = "packages/core" }
|
||||||
dioxus-core-macro = { path = "packages/core-macro" }
|
dioxus-core-macro = { path = "packages/core-macro" }
|
||||||
dioxus-router = { path = "packages/router" }
|
dioxus-router = { path = "packages/router" }
|
||||||
|
dioxus-router-macro = { path = "packages/router-macro" }
|
||||||
dioxus-html = { path = "packages/html" }
|
dioxus-html = { path = "packages/html" }
|
||||||
dioxus-hooks = { path = "packages/hooks" }
|
dioxus-hooks = { path = "packages/hooks" }
|
||||||
dioxus-web = { path = "packages/web" }
|
dioxus-web = { path = "packages/web" }
|
||||||
|
|
|
@ -3,7 +3,7 @@ name = "dioxus-guide"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Dioxus guide, including testable examples"
|
description = "Dioxus guide, including testable examples"
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -74,10 +74,9 @@ fn app(cx: Scope<usize>) -> Element {
|
||||||
button {
|
button {
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
to_owned![count];
|
to_owned![count];
|
||||||
let sc = cx.sc();
|
|
||||||
async move {
|
async move {
|
||||||
// Call the server function just like a local async function
|
// Call the server function just like a local async function
|
||||||
if let Ok(new_count) = double_server(sc, *count.current()).await {
|
if let Ok(new_count) = double_server(*count.current()).await {
|
||||||
count.set(new_count);
|
count.set(new_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +88,8 @@ fn app(cx: Scope<usize>) -> Element {
|
||||||
|
|
||||||
// We use the "getcbor" encoding to make caching easier
|
// We use the "getcbor" encoding to make caching easier
|
||||||
#[server(DoubleServer, "", "getcbor")]
|
#[server(DoubleServer, "", "getcbor")]
|
||||||
async fn double_server(cx: DioxusServerContext, number: usize) -> Result<usize, ServerFnError> {
|
async fn double_server(number: usize) -> Result<usize, ServerFnError> {
|
||||||
|
let cx = server_context();
|
||||||
// Perform some expensive computation or access a database on the server
|
// Perform some expensive computation or access a database on the server
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
let result = number * 2;
|
let result = number * 2;
|
||||||
|
|
|
@ -92,10 +92,9 @@ fn app(cx: Scope<usize>) -> Element {
|
||||||
button {
|
button {
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
to_owned![count];
|
to_owned![count];
|
||||||
let sc = cx.sc();
|
|
||||||
async move {
|
async move {
|
||||||
// Call the server function just like a local async function
|
// Call the server function just like a local async function
|
||||||
if let Ok(new_count) = double_server(sc, *count.current()).await {
|
if let Ok(new_count) = double_server(*count.current()).await {
|
||||||
count.set(new_count);
|
count.set(new_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,10 +106,11 @@ fn app(cx: Scope<usize>) -> Element {
|
||||||
|
|
||||||
// We use the "getcbor" encoding to make caching easier
|
// We use the "getcbor" encoding to make caching easier
|
||||||
#[server(DoubleServer, "", "getcbor")]
|
#[server(DoubleServer, "", "getcbor")]
|
||||||
async fn double_server(cx: DioxusServerContext, number: usize) -> Result<usize, ServerFnError> {
|
async fn double_server(number: usize) -> Result<usize, ServerFnError> {
|
||||||
// Perform some expensive computation or access a database on the server
|
// Perform some expensive computation or access a database on the server
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
let result = number * 2;
|
let result = number * 2;
|
||||||
|
let cx = server_context();
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"User Agent {:?}",
|
"User Agent {:?}",
|
||||||
|
|
|
@ -21,10 +21,12 @@
|
||||||
- [Hooks & Component State](interactivity/hooks.md)
|
- [Hooks & Component State](interactivity/hooks.md)
|
||||||
- [User Input](interactivity/user_input.md)
|
- [User Input](interactivity/user_input.md)
|
||||||
- [Sharing State](interactivity/sharing_state.md)
|
- [Sharing State](interactivity/sharing_state.md)
|
||||||
|
- [Memoization](interactivity/memoization.md)
|
||||||
- [Custom Hooks](interactivity/custom_hooks.md)
|
- [Custom Hooks](interactivity/custom_hooks.md)
|
||||||
- [Dynamic Rendering](interactivity/dynamic_rendering.md)
|
- [Dynamic Rendering](interactivity/dynamic_rendering.md)
|
||||||
- [Routing](interactivity/router.md)
|
- [Routing](interactivity/router.md)
|
||||||
- [Async](async/index.md)
|
- [Async](async/index.md)
|
||||||
|
- [UseEffect](async/use_effect.md)
|
||||||
- [UseFuture](async/use_future.md)
|
- [UseFuture](async/use_future.md)
|
||||||
- [UseCoroutine](async/use_coroutine.md)
|
- [UseCoroutine](async/use_coroutine.md)
|
||||||
- [Spawning Futures](async/spawn.md)
|
- [Spawning Futures](async/spawn.md)
|
||||||
|
|
|
@ -94,8 +94,10 @@ Calling `deref` or `deref_mut` is actually more complex than it seems. When a va
|
||||||
|
|
||||||
Sometimes you want a signal to propagate across your app, either through far-away siblings or through deeply-nested components. In these cases, we use Dirac: Dioxus's first-class state management toolkit. Dirac atoms automatically implement the Signal API. This component will bind the input element to the `TITLE` atom.
|
Sometimes you want a signal to propagate across your app, either through far-away siblings or through deeply-nested components. In these cases, we use Dirac: Dioxus's first-class state management toolkit. Dirac atoms automatically implement the Signal API. This component will bind the input element to the `TITLE` atom.
|
||||||
|
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
const TITLE: Atom<String> = || "".to_string();
|
const TITLE: Atom<String> = Atom(|| "".to_string());
|
||||||
|
|
||||||
const Provider: Component = |cx|{
|
const Provider: Component = |cx|{
|
||||||
let title = use_signal(cx, &TITLE);
|
let title = use_signal(cx, &TITLE);
|
||||||
render!(input { value: title })
|
render!(input { value: title })
|
||||||
|
@ -131,7 +133,8 @@ By default, Dioxus is limited when you use iter/map. With the `For` component, y
|
||||||
Dioxus automatically understands how to use your signals when mixed with iterators through `Deref`/`DerefMut`. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster.
|
Dioxus automatically understands how to use your signals when mixed with iterators through `Deref`/`DerefMut`. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster.
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
const DICT: AtomFamily<String, String> = |_| {};
|
const DICT: AtomFamily<String, String> = AtomFamily(|_| {});
|
||||||
|
|
||||||
const List: Component = |cx|{
|
const List: Component = |cx|{
|
||||||
let dict = use_signal(cx, &DICT);
|
let dict = use_signal(cx, &DICT);
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
|
@ -142,14 +145,6 @@ const List: Component = |cx|{
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Remote Signals
|
|
||||||
|
|
||||||
Apps that use signals will enjoy a pleasant hybrid of server-side and client-side rendering.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## How does it work?
|
## How does it work?
|
||||||
|
|
||||||
Signals internally use Dioxus' asynchronous rendering infrastructure to perform updates out of the tree.
|
Signals internally use Dioxus' asynchronous rendering infrastructure to perform updates out of the tree.
|
||||||
|
|
|
@ -21,7 +21,7 @@ fn runs_in_browser() {
|
||||||
Then, when you run
|
Then, when you run
|
||||||
|
|
||||||
```console
|
```console
|
||||||
dioxus test --chrome
|
dx test --chrome
|
||||||
```
|
```
|
||||||
|
|
||||||
Dioxus will build and test your code using the Chrome browser as a harness.
|
Dioxus will build and test your code using the Chrome browser as a harness.
|
||||||
|
|
|
@ -143,7 +143,7 @@ async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
|
||||||
We can combine coroutines with [Fermi](https://docs.rs/fermi/latest/fermi/index.html) to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state _within_ a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your _actual_ state does not need to be tied up in a system like Fermi or Redux – the only Atoms that need to exist are those that are used to drive the display/UI.
|
We can combine coroutines with [Fermi](https://docs.rs/fermi/latest/fermi/index.html) to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state _within_ a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your _actual_ state does not need to be tied up in a system like Fermi or Redux – the only Atoms that need to exist are those that are used to drive the display/UI.
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
static USERNAME: Atom<String> = |_| "default".to_string();
|
static USERNAME: Atom<String> = Atom(|_| "default".to_string());
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let atoms = use_atom_root(cx);
|
let atoms = use_atom_root(cx);
|
||||||
|
@ -156,7 +156,7 @@ fn app(cx: Scope) -> Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Banner(cx: Scope) -> Element {
|
fn Banner(cx: Scope) -> Element {
|
||||||
let username = use_read(cx, USERNAME);
|
let username = use_read(cx, &USERNAME);
|
||||||
|
|
||||||
cx.render(rsx!{
|
cx.render(rsx!{
|
||||||
h1 { "Welcome back, {username}" }
|
h1 { "Welcome back, {username}" }
|
||||||
|
@ -174,8 +174,8 @@ enum SyncAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
|
async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
|
||||||
let username = atoms.write(USERNAME);
|
let username = atoms.write(&USERNAME);
|
||||||
let errors = atoms.write(ERRORS);
|
let errors = atoms.write(&ERRORS);
|
||||||
|
|
||||||
while let Ok(msg) = rx.next().await {
|
while let Ok(msg) = rx.next().await {
|
||||||
match msg {
|
match msg {
|
||||||
|
|
41
docs/guide/src/en/async/use_effect.md
Normal file
41
docs/guide/src/en/async/use_effect.md
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# UseEffect
|
||||||
|
|
||||||
|
[`use_effect`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_effect.html) lets you run a callback that returns a future, which will be re-run when its [dependencies](#dependencies) change. This is useful to syncrhonize with external events.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
You can make the callback re-run when some value changes. For example, you might want to fetch a user's data only when the user id changes. You can provide a tuple of "dependencies" to the hook. It will automatically re-run it when any of those dependencies change.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
#[inline_props]
|
||||||
|
fn Profile(cx: Scope, id: usize) -> Element {
|
||||||
|
let name = use_state(cx, || None);
|
||||||
|
|
||||||
|
// Only fetch the user data when the id changes.
|
||||||
|
use_effect(cx, (id,), |(id,)| {
|
||||||
|
to_owned![name];
|
||||||
|
async move {
|
||||||
|
let user = fetch_user(id).await;
|
||||||
|
name.set(user.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Because the dependencies are empty, this will only run once.
|
||||||
|
// An empty tuple is always equal to an empty tuple.
|
||||||
|
use_effect(cx, (), |()| async move {
|
||||||
|
println!("Hello, World!");
|
||||||
|
});
|
||||||
|
|
||||||
|
let name = name.get().clone().unwrap_or("Loading...".to_string());
|
||||||
|
|
||||||
|
render!(
|
||||||
|
p { "{name}" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
render!(Profile { id: 0 })
|
||||||
|
}
|
||||||
|
```
|
|
@ -113,14 +113,14 @@ enum InputError {
|
||||||
TooShort,
|
TooShort,
|
||||||
}
|
}
|
||||||
|
|
||||||
static INPUT_ERROR: Atom<InputError> = |_| InputError::None;
|
static INPUT_ERROR: Atom<InputError> = Atom(|_| InputError::None);
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree.
|
Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree.
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
fn TopLevel(cx: Scope) -> Element {
|
fn TopLevel(cx: Scope) -> Element {
|
||||||
let error = use_read(cx, INPUT_ERROR);
|
let error = use_read(cx, &INPUT_ERROR);
|
||||||
|
|
||||||
match error {
|
match error {
|
||||||
TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
|
TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
|
||||||
|
@ -134,7 +134,7 @@ Now, whenever a downstream component has an error in its actions, it can simply
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
fn Commandline(cx: Scope) -> Element {
|
fn Commandline(cx: Scope) -> Element {
|
||||||
let set_error = use_set(cx, INPUT_ERROR);
|
let set_error = use_set(cx, &INPUT_ERROR);
|
||||||
|
|
||||||
cx.render(rsx!{
|
cx.render(rsx!{
|
||||||
input {
|
input {
|
||||||
|
|
|
@ -14,13 +14,13 @@ We start will a hello world program. This program renders a desktop app with the
|
||||||
|
|
||||||
## The rsx! Macro
|
## The rsx! Macro
|
||||||
|
|
||||||
Before the Rust compiler runs the program, it will expand all macros. Here is what the hello world example looks like expanded:
|
Before the Rust compiler runs the program, it will expand all [macros](https://doc.rust-lang.org/reference/procedural-macros.html). Here is what the hello world example looks like expanded:
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
{{#include ../../../examples/readme_expanded.rs}}
|
{{#include ../../../examples/readme_expanded.rs}}
|
||||||
```
|
```
|
||||||
|
|
||||||
The rsx macro separates the static parts of the rsx (the template) and the dynamic parts (the dynamic_nodes and dynamic_attributes).
|
The rsx macro separates the static parts of the rsx (the template) and the dynamic parts (the [dynamic_nodes](https://docs.rs/dioxus-core/0.3.2/dioxus_core/prelude/struct.VNode.html#structfield.dynamic_nodes) and [dynamic_attributes](https://docs.rs/dioxus-core/0.3.2/dioxus_core/prelude/struct.VNode.html#structfield.dynamic_attrs)).
|
||||||
|
|
||||||
The static template only contains the parts of the rsx that cannot change at runtime with holes for the dynamic parts:
|
The static template only contains the parts of the rsx that cannot change at runtime with holes for the dynamic parts:
|
||||||
|
|
||||||
|
@ -32,17 +32,17 @@ The dynamic_nodes and dynamic_attributes are the parts of the rsx that can chang
|
||||||
|
|
||||||
## Launching the App
|
## Launching the App
|
||||||
|
|
||||||
The app is launched by calling the `launch` function with the root component. Internally, this function will create a new web view using [wry](https://docs.rs/wry/latest/wry/) and create a virtual dom with the root component. This guide will not explain the renderer in-depth, but you can read more about it in the [custom renderer](/guide/custom-renderer) section.
|
The app is launched by calling the `launch` function with the root component. Internally, this function will create a new web view using [wry](https://docs.rs/wry/latest/wry/) and create a virtual dom with the root component (`fn app()` in the readme example). This guide will not explain the renderer in-depth, but you can read more about it in the [custom renderer](/guide/custom-renderer) section.
|
||||||
|
|
||||||
## The Virtual DOM
|
## The Virtual DOM
|
||||||
|
|
||||||
Before we dive into the initial render in the virtual dom, we need to discuss what the virtual dom is. The virtual dom is a representation of the dom that is used to diff the current dom from the new dom. This diff is then used to create a list of mutations that need to be applied to the dom.
|
Before we dive into the initial render in the virtual DOM, we need to discuss what the virtual DOM is. The virtual DOM is a representation of the DOM that is used to diff the current DOM from the new DOM. This diff is then used to create a list of mutations that need to be applied to the DOM to bring it into sync with the virtual DOM.
|
||||||
|
|
||||||
The Virtual Dom roughly looks like this:
|
The Virtual DOM roughly looks like this:
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
pub struct VirtualDom {
|
pub struct VirtualDom {
|
||||||
// All the templates that have been created or set durring hot reloading
|
// All the templates that have been created or set during hot reloading
|
||||||
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
|
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
|
||||||
|
|
||||||
// A slab of all the scopes that have been created
|
// A slab of all the scopes that have been created
|
||||||
|
@ -63,64 +63,74 @@ pub struct VirtualDom {
|
||||||
```
|
```
|
||||||
|
|
||||||
> What is a [slab](https://docs.rs/slab/latest/slab/)?
|
> What is a [slab](https://docs.rs/slab/latest/slab/)?
|
||||||
|
>
|
||||||
> A slab acts like a hashmap with integer keys if you don't care about the value of the keys. It is internally backed by a dense vector which makes it more efficient than a hashmap. When you insert a value into a slab, it returns an integer key that you can use to retrieve the value later.
|
> A slab acts like a hashmap with integer keys if you don't care about the value of the keys. It is internally backed by a dense vector which makes it more efficient than a hashmap. When you insert a value into a slab, it returns an integer key that you can use to retrieve the value later.
|
||||||
|
|
||||||
> How does Dioxus use slabs?
|
> How does Dioxus use slabs?
|
||||||
> Dioxus uses "synchronized slabs" to communicate between the renderer and the VDOM. When an node is created in the Virtual Dom, a ElementId is passed along with the mutation to the renderer to identify the node. These ids are used by the Virtual Dom to reference that nodes in future mutations like setting an attribute on a node or removing a node.
|
>
|
||||||
> When the renderer sends an event to the Virtual Dom, it sends the ElementId of the node that the event was triggered on. The Virtual Dom uses this id to find the node in the slab and then run the necessary event handlers.
|
> Dioxus uses "synchronized slabs" to communicate between the renderer and the VDOM. When a node is created in the Virtual DOM, an (elementId, mutation) pair is passed to the renderer to identify that node, which the renderer will then render in actual DOM. These ids are also used by the Virtual Dom to reference that node in future mutations, like setting an attribute on a node or removing a node. When the renderer sends an event to the Virtual Dom, it sends the ElementId of the node that the event was triggered on. The Virtual DOM uses this id to find that node in the slab and then run the necessary event handlers.
|
||||||
|
|
||||||
The virtual dom is a tree of scopes. A new scope is created for every component when it is first rendered and recycled when the component is unmounted.
|
The virtual DOM is a tree of scopes. A new `Scope` is created for every component when it is first rendered and recycled when the component is unmounted.
|
||||||
|
|
||||||
Scopes serve three main purposes:
|
Scopes serve three main purposes:
|
||||||
|
|
||||||
1. They store the state of hooks used by the component
|
1. They store the state of hooks used by the component
|
||||||
2. They store the state for the context API
|
2. They store the state for the context API (for example: using
|
||||||
3. They store the current and previous VNode that was rendered for diffing
|
[use_shared_state_provider](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_shared_state_provider.html)).
|
||||||
|
3. They store the current and previous versions of the `VNode` that was rendered, so they can be
|
||||||
|
diffed to generate the set of mutations needed to re-render it.
|
||||||
|
|
||||||
### The Initial Render
|
### The Initial Render
|
||||||
|
|
||||||
The root scope is created and rebuilt:
|
The root scope is created and rebuilt:
|
||||||
|
|
||||||
1. The root component is run
|
1. The root component is run
|
||||||
2. The root component returns a VNode
|
2. The root component returns a `VNode`
|
||||||
3. Mutations for the VNode are created and added to the mutation list (this may involve creating new child components)
|
3. Mutations for this `VNode` are created and added to the mutation list (this may involve creating new child components)
|
||||||
4. The VNode is stored in the root scope
|
4. The `VNode` is stored in the root's `Scope`.
|
||||||
|
|
||||||
After the root scope is built, the mutations are sent to the renderer to be applied to the dom.
|
After the root's `Scope` is built, all generated mutations are sent to the renderer, which applies them to the DOM.
|
||||||
|
|
||||||
After the initial render, the root scope looks like this:
|
After the initial render, the root `Scope` looks like this:
|
||||||
|
|
||||||
[![](https://mermaid.ink/img/pako:eNqtVE1P4zAQ_SuzPrWikRpWXCLtBRDisItWsOxhCaqM7RKricdyJrQV8N93QtvQNCkfEnOynydv3nxkHoVCbUQipjnOVSYDwc_L1AFbWd3dB-kzuEQkuFLoDUwDFkCZAek9nGDh0RlHK__atA1GkUUHf45f0YbppAqB_aOzIAvz-t7-chN_Y-1bw1WSJKsglIu2w9tktWXxIIuHURT5XCqTYa5NmDguw2R8c5MKq2GcgF46WTB_jafi9rZL0yi5q4jQTSrf9altO4okCn1Ratwyz55Qxuku2ITlTMgs6HCQimsPmb3PvqVi-L5gjXP3QcnxWnL8JZLrwGvR31n0KV-Bx6-r-oVkT_-3G1S-NQLbk9i8rj7udP2cixed2QcDCitHJiQw7ub3EVlNecrPjudG2-6soFO5VbMECmR9T5OnlUY4-AFxfw9aTFst3McU9TK1Otm6NEn_DubBYlX2_dglLXOz48FgwJmJ5lZTlhz6xWgNaFnyDgpymcARHO0W2a9J_l5w2wYXvHuGPcqaQ-rESBQmFNJq3nCPNZoK3l4sUSR81DLMUpG6Z_aTFeHV0imRUKjMSFReSzKnVnKGhUimMi8ZNdoShl-rlfmyOUfCS_cPcePz_B_Wl4pc?type=png)](https://mermaid.live/edit#pako:eNqtVE1P4zAQ_SuzPrWikRpWXCLtBRDisItWsOxhCaqM7RKricdyJrQV8N93QtvQNCkfEnOynydv3nxkHoVCbUQipjnOVSYDwc_L1AFbWd3dB-kzuEQkuFLoDUwDFkCZAek9nGDh0RlHK__atA1GkUUHf45f0YbppAqB_aOzIAvz-t7-chN_Y-1bw1WSJKsglIu2w9tktWXxIIuHURT5XCqTYa5NmDguw2R8c5MKq2GcgF46WTB_jafi9rZL0yi5q4jQTSrf9altO4okCn1Ratwyz55Qxuku2ITlTMgs6HCQimsPmb3PvqVi-L5gjXP3QcnxWnL8JZLrwGvR31n0KV-Bx6-r-oVkT_-3G1S-NQLbk9i8rj7udP2cixed2QcDCitHJiQw7ub3EVlNecrPjudG2-6soFO5VbMECmR9T5OnlUY4-AFxfw9aTFst3McU9TK1Otm6NEn_DubBYlX2_dglLXOz48FgwJmJ5lZTlhz6xWgNaFnyDgpymcARHO0W2a9J_l5w2wYXvHuGPcqaQ-rESBQmFNJq3nCPNZoK3l4sUSR81DLMUpG6Z_aTFeHV0imRUKjMSFReSzKnVnKGhUimMi8ZNdoShl-rlfmyOUfCS_cPcePz_B_Wl4pc)
|
[![](https://mermaid.ink/img/pako:eNqtVE1P4zAQ_SuzPrWikRpWXCLtBRDisItWsOxhCaqM7RKricdyJrQV8N93QtvQNCkfEnOynydv3nxkHoVCbUQipjnOVSYDwc_L1AFbWd3dB-kzuEQkuFLoDUwDFkCZAek9nGDh0RlHK__atA1GkUUHf45f0YbppAqB_aOzIAvz-t7-chN_Y-1bw1WSJKsglIu2w9tktWXxIIuHURT5XCqTYa5NmDguw2R8c5MKq2GcgF46WTB_jafi9rZL0yi5q4jQTSrf9altO4okCn1Ratwyz55Qxuku2ITlTMgs6HCQimsPmb3PvqVi-L5gjXP3QcnxWnL8JZLrwGvR31n0KV-Bx6-r-oVkT_-3G1S-NQLbk9i8rj7udP2cixed2QcDCitHJiQw7ub3EVlNecrPjudG2-6soFO5VbMECmR9T5OnlUY4-AFxfw9aTFst3McU9TK1Otm6NEn_DubBYlX2_dglLXOz48FgwJmJ5lZTlhz6xWgNaFnyDgpymcARHO0W2a9J_l5w2wYXvHuGPcqaQ-rESBQmFNJq3nCPNZoK3l4sUSR81DLMUpG6Z_aTFeHV0imRUKjMSFReSzKnVnKGhUimMi8ZNdoShl-rlfmyOUfCS_cPcePz_B_Wl4pc?type=png)](https://mermaid.live/edit#pako:eNqtVE1P4zAQ_SuzPrWikRpWXCLtBRDisItWsOxhCaqM7RKricdyJrQV8N93QtvQNCkfEnOynydv3nxkHoVCbUQipjnOVSYDwc_L1AFbWd3dB-kzuEQkuFLoDUwDFkCZAek9nGDh0RlHK__atA1GkUUHf45f0YbppAqB_aOzIAvz-t7-chN_Y-1bw1WSJKsglIu2w9tktWXxIIuHURT5XCqTYa5NmDguw2R8c5MKq2GcgF46WTB_jafi9rZL0yi5q4jQTSrf9altO4okCn1Ratwyz55Qxuku2ITlTMgs6HCQimsPmb3PvqVi-L5gjXP3QcnxWnL8JZLrwGvR31n0KV-Bx6-r-oVkT_-3G1S-NQLbk9i8rj7udP2cixed2QcDCitHJiQw7ub3EVlNecrPjudG2-6soFO5VbMECmR9T5OnlUY4-AFxfw9aTFst3McU9TK1Otm6NEn_DubBYlX2_dglLXOz48FgwJmJ5lZTlhz6xWgNaFnyDgpymcARHO0W2a9J_l5w2wYXvHuGPcqaQ-rESBQmFNJq3nCPNZoK3l4sUSR81DLMUpG6Z_aTFeHV0imRUKjMSFReSzKnVnKGhUimMi8ZNdoShl-rlfmyOUfCS_cPcePz_B_Wl4pc)
|
||||||
|
|
||||||
### Waiting for Events
|
### Waiting for Events
|
||||||
|
|
||||||
The Virtual Dom will only ever rerender a scope if it is marked as dirty. Each hook is responsible for marking the scope as dirty if the state has changed. Hooks can mark a scope as dirty by sending a message to the Virtual Dom's channel.
|
The Virtual DOM will only ever re-render a `Scope` if it is marked as dirty. Each hook is responsible for marking the `Scope` as dirty if the state has changed. Hooks can mark a scope as dirty by sending a message to the Virtual Dom's channel. You can see the [implementations](https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks) for the hooks dioxus includes by default on how this is done. Calling `needs_update()` on a hook will also cause it to mark its scope as dirty.
|
||||||
|
|
||||||
There are generally two ways a scope is marked as dirty:
|
There are generally two ways a scope is marked as dirty:
|
||||||
|
|
||||||
1. The renderer triggers an event: This causes an event listener to be called if needed which may mark a component as dirty
|
1. The renderer triggers an event: An event listener on this event may be called, which may mark a
|
||||||
2. The renderer calls wait for work: This polls futures which may mark a component as dirty
|
component as dirty, if processing the event resulted in any generated any mutations.
|
||||||
|
2. The renderer calls
|
||||||
|
[`wait_for_work`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.wait_for_work):
|
||||||
|
This polls dioxus internal future queue. One of these futures may mark a component as dirty.
|
||||||
|
|
||||||
Once at least one scope is marked as dirty, the renderer can call `render_with_deadline` to diff the dirty scopes.
|
Once at least one `Scope` is marked as dirty, the renderer can call [`render_with_deadline`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.render_with_deadline) to diff the dirty scopes.
|
||||||
|
|
||||||
### Diffing Scopes
|
### Diffing Scopes
|
||||||
|
|
||||||
If the user clicked the "up high" button, the root scope would be marked as dirty by the use_state hook. Once the desktop renderer calls `render_with_deadline`, the root scope would be diffed.
|
When a user clicks the "up high" button, the root `Scope` will be marked as dirty by the `use_state` hook. The desktop renderer will then call `render_with_deadline`, which will diff the root `Scope`.
|
||||||
|
|
||||||
To start the diffing process, the component is run. After the root component is run it will look like this:
|
To start the diffing process, the component function is run. After the root component is run it, the root `Scope` will look like this:
|
||||||
|
|
||||||
[![](https://mermaid.ink/img/pako:eNrFVlFP2zAQ_iuen0BrpCaIl0i8AEJ72KQJtpcRFBnbJVYTn-U4tBXw33dpG5M2CetoBfdkny_ffb67fPIT5SAkjekkhxnPmHXk-3WiCVpZ3T9YZjJyDeDIDQcjycRCQVwmCTOGXEBhQEvtVvG1CWUldwo0-XX-6vVIF5W1GB9cWVbI1_PNL5v8jW3uPFbpmFOc2HK-GfA2WG1ZeJSFx0EQmJxxmUEupE01liEd394mVAkyjolYaFYgfu1P6N1dF8Yzua-cA51WphtTWzsLc872Zan9CnEGUkktuk6fFm_i5NxFRwn9bUimHrIvCT3-N2EBM70j5XBNOTwI5TrxmvQJkr7ELcHx67Jeggz0v92g8q0RaE-iP1193On6NyxecKUeJeFQaSdtTMLu_Xah5ctT_u94Nty2ZwU0zxWfxqQA5PecPq84kq9nfRw7SK0WDiEFZ4O37d34S_-08lFBVfb92KVb5HIrAp0WpjKYKeGyODLz0dohWIkaZNkiJqfkdLvIH6oRaTSoEmm0n06k0a5K0ZdpL61Io0Yt0nfpxc7UQ0_9cJrhyZ8syX-6brS706Mc489Vjja7fbWj3cxDqIdfJJqOaCFtwZTAV8hT7U0ovjBQRmiMS8HsNKGJfsE4Vjm4WWhOY2crOaKVEczJS8WwgAWNJywv0SuFcmB_rJ41y9fNiBqm_wA0MS9_AUuAiy0?type=png)](https://mermaid.live/edit#pako:eNrFVlFP2zAQ_iuen0BrpCaIl0i8AEJ72KQJtpcRFBnbJVYTn-U4tBXw33dpG5M2CetoBfdkny_ffb67fPIT5SAkjekkhxnPmHXk-3WiCVpZ3T9YZjJyDeDIDQcjycRCQVwmCTOGXEBhQEvtVvG1CWUldwo0-XX-6vVIF5W1GB9cWVbI1_PNL5v8jW3uPFbpmFOc2HK-GfA2WG1ZeJSFx0EQmJxxmUEupE01liEd394mVAkyjolYaFYgfu1P6N1dF8Yzua-cA51WphtTWzsLc872Zan9CnEGUkktuk6fFm_i5NxFRwn9bUimHrIvCT3-N2EBM70j5XBNOTwI5TrxmvQJkr7ELcHx67Jeggz0v92g8q0RaE-iP1193On6NyxecKUeJeFQaSdtTMLu_Xah5ctT_u94Nty2ZwU0zxWfxqQA5PecPq84kq9nfRw7SK0WDiEFZ4O37d34S_-08lFBVfb92KVb5HIrAp0WpjKYKeGyODLz0dohWIkaZNkiJqfkdLvIH6oRaTSoEmm0n06k0a5K0ZdpL61Io0Yt0nfpxc7UQ0_9cJrhyZ8syX-6brS706Mc489Vjja7fbWj3cxDqIdfJJqOaCFtwZTAV8hT7U0ovjBQRmiMS8HsNKGJfsE4Vjm4WWhOY2crOaKVEczJS8WwgAWNJywv0SuFcmB_rJ41y9fNiBqm_wA0MS9_AUuAiy0)
|
[![](https://mermaid.ink/img/pako:eNrFVlFP2zAQ_iuen0BrpCaIl0i8AEJ72KQJtpcRFBnbJVYTn-U4tBXw33dpG5M2CetoBfdkny_ffb67fPIT5SAkjekkhxnPmHXk-3WiCVpZ3T9YZjJyDeDIDQcjycRCQVwmCTOGXEBhQEvtVvG1CWUldwo0-XX-6vVIF5W1GB9cWVbI1_PNL5v8jW3uPFbpmFOc2HK-GfA2WG1ZeJSFx0EQmJxxmUEupE01liEd394mVAkyjolYaFYgfu1P6N1dF8Yzua-cA51WphtTWzsLc872Zan9CnEGUkktuk6fFm_i5NxFRwn9bUimHrIvCT3-N2EBM70j5XBNOTwI5TrxmvQJkr7ELcHx67Jeggz0v92g8q0RaE-iP1193On6NyxecKUeJeFQaSdtTMLu_Xah5ctT_u94Nty2ZwU0zxWfxqQA5PecPq84kq9nfRw7SK0WDiEFZ4O37d34S_-08lFBVfb92KVb5HIrAp0WpjKYKeGyODLz0dohWIkaZNkiJqfkdLvIH6oRaTSoEmm0n06k0a5K0ZdpL61Io0Yt0nfpxc7UQ0_9cJrhyZ8syX-6brS706Mc489Vjja7fbWj3cxDqIdfJJqOaCFtwZTAV8hT7U0ovjBQRmiMS8HsNKGJfsE4Vjm4WWhOY2crOaKVEczJS8WwgAWNJywv0SuFcmB_rJ41y9fNiBqm_wA0MS9_AUuAiy0?type=png)](https://mermaid.live/edit#pako:eNrFVlFP2zAQ_iuen0BrpCaIl0i8AEJ72KQJtpcRFBnbJVYTn-U4tBXw33dpG5M2CetoBfdkny_ffb67fPIT5SAkjekkhxnPmHXk-3WiCVpZ3T9YZjJyDeDIDQcjycRCQVwmCTOGXEBhQEvtVvG1CWUldwo0-XX-6vVIF5W1GB9cWVbI1_PNL5v8jW3uPFbpmFOc2HK-GfA2WG1ZeJSFx0EQmJxxmUEupE01liEd394mVAkyjolYaFYgfu1P6N1dF8Yzua-cA51WphtTWzsLc872Zan9CnEGUkktuk6fFm_i5NxFRwn9bUimHrIvCT3-N2EBM70j5XBNOTwI5TrxmvQJkr7ELcHx67Jeggz0v92g8q0RaE-iP1193On6NyxecKUeJeFQaSdtTMLu_Xah5ctT_u94Nty2ZwU0zxWfxqQA5PecPq84kq9nfRw7SK0WDiEFZ4O37d34S_-08lFBVfb92KVb5HIrAp0WpjKYKeGyODLz0dohWIkaZNkiJqfkdLvIH6oRaTSoEmm0n06k0a5K0ZdpL61Io0Yt0nfpxc7UQ0_9cJrhyZ8syX-6brS706Mc489Vjja7fbWj3cxDqIdfJJqOaCFtwZTAV8hT7U0ovjBQRmiMS8HsNKGJfsE4Vjm4WWhOY2crOaKVEczJS8WwgAWNJywv0SuFcmB_rJ41y9fNiBqm_wA0MS9_AUuAiy0)
|
||||||
|
|
||||||
Next, the Virtual Dom will compare the new VNode with the previous VNode and only update the parts of the tree that have changed.
|
Next, the Virtual DOM will compare the new VNode with the previous VNode and only update the parts of the tree that have changed. Because of this approach, when a component is re-rendered only the parts of the tree that have changed will be updated in the DOM by the renderer.
|
||||||
|
|
||||||
When a component is re-rendered, the Virtual Dom will compare the new VNode with the previous VNode and only update the parts of the tree that have changed.
|
|
||||||
|
|
||||||
The diffing algorithm goes through the list of dynamic attributes and nodes and compares them to the previous VNode. If the attribute or node has changed, a mutation that describes the change is added to the mutation list.
|
The diffing algorithm goes through the list of dynamic attributes and nodes and compares them to the previous VNode. If the attribute or node has changed, a mutation that describes the change is added to the mutation list.
|
||||||
|
|
||||||
Here is what the diffing algorithm looks like for the root scope (red lines indicate that a mutation was generated, and green lines indicate that no mutation was generated)
|
Here is what the diffing algorithm looks like for the root `Scope` (red lines indicate that a mutation was generated, and green lines indicate that no mutation was generated)
|
||||||
|
|
||||||
[![](https://mermaid.ink/img/pako:eNrFlFFPwjAQx7_KpT7Kko2Elya8qCE-aGLAJ5khpe1Yw9Zbug4k4He3OJjbGPig0T5t17tf_nf777aEo5CEkijBNY-ZsfAwDjW4kxfzhWFZDGNECxOOmYTIYAo2lsCyDG4xzVBLbcv8_RHKSG4V6orSIN0Wxrh8b2RYKr_uTyubd1W92GiWKg7aac6bOU3G803HbVk82xfP_Ok0JEqAT-FeLWJvpFYSOBbaSkMhCMnra5MgtfhWFrPWqHlhL2urT6atbU-oa0PNE8WXFFJ0-nazXakRroddGk9IwYEUnCd5w7Pddr5UTT8ZuVJY5F0fM7ebRLYyXNDgUnprJWxM-9lb7xAQLHe-M2xDYQCD9pD_2hez_kVn-P_rjLq6n3qjYv2iO5qz9DyvPdyv1ETp5eTTJ_7BGvQq8v1TVtl5jXUcRRcrqFh-dI4VtFlBN6t_ynLNkh5JpUmZEm5rbvfhkLiN6H4BQt2jYGYZklC_uzxWWJxsNCfUmkL2SJEJZuWdYs4cKaERS3IXlUJZNI_lGv7cxj2SMf2CeMx5_wBcbK19?type=png)](https://mermaid.live/edit#pako:eNrFlFFPwjAQx7_KpT7Kko2Elya8qCE-aGLAJ5khpe1Yw9Zbug4k4He3OJjbGPig0T5t17tf_nf777aEo5CEkijBNY-ZsfAwDjW4kxfzhWFZDGNECxOOmYTIYAo2lsCyDG4xzVBLbcv8_RHKSG4V6orSIN0Wxrh8b2RYKr_uTyubd1W92GiWKg7aac6bOU3G803HbVk82xfP_Ok0JEqAT-FeLWJvpFYSOBbaSkMhCMnra5MgtfhWFrPWqHlhL2urT6atbU-oa0PNE8WXFFJ0-nazXakRroddGk9IwYEUnCd5w7Pddr5UTT8ZuVJY5F0fM7ebRLYyXNDgUnprJWxM-9lb7xAQLHe-M2xDYQCD9pD_2hez_kVn-P_rjLq6n3qjYv2iO5qz9DyvPdyv1ETp5eTTJ_7BGvQq8v1TVtl5jXUcRRcrqFh-dI4VtFlBN6t_ynLNkh5JpUmZEm5rbvfhkLiN6H4BQt2jYGYZklC_uzxWWJxsNCfUmkL2SJEJZuWdYs4cKaERS3IXlUJZNI_lGv7cxj2SMf2CeMx5_wBcbK19)
|
[![](https://mermaid.ink/img/pako:eNrFlFFPwjAQx7_KpT7Kko2Elya8qCE-aGLAJ5khpe1Yw9Zbug4k4He3OJjbGPig0T5t17tf_nf777aEo5CEkijBNY-ZsfAwDjW4kxfzhWFZDGNECxOOmYTIYAo2lsCyDG4xzVBLbcv8_RHKSG4V6orSIN0Wxrh8b2RYKr_uTyubd1W92GiWKg7aac6bOU3G803HbVk82xfP_Ok0JEqAT-FeLWJvpFYSOBbaSkMhCMnra5MgtfhWFrPWqHlhL2urT6atbU-oa0PNE8WXFFJ0-nazXakRroddGk9IwYEUnCd5w7Pddr5UTT8ZuVJY5F0fM7ebRLYyXNDgUnprJWxM-9lb7xAQLHe-M2xDYQCD9pD_2hez_kVn-P_rjLq6n3qjYv2iO5qz9DyvPdyv1ETp5eTTJ_7BGvQq8v1TVtl5jXUcRRcrqFh-dI4VtFlBN6t_ynLNkh5JpUmZEm5rbvfhkLiN6H4BQt2jYGYZklC_uzxWWJxsNCfUmkL2SJEJZuWdYs4cKaERS3IXlUJZNI_lGv7cxj2SMf2CeMx5_wBcbK19?type=png)](https://mermaid.live/edit#pako:eNrFlFFPwjAQx7_KpT7Kko2Elya8qCE-aGLAJ5khpe1Yw9Zbug4k4He3OJjbGPig0T5t17tf_nf777aEo5CEkijBNY-ZsfAwDjW4kxfzhWFZDGNECxOOmYTIYAo2lsCyDG4xzVBLbcv8_RHKSG4V6orSIN0Wxrh8b2RYKr_uTyubd1W92GiWKg7aac6bOU3G803HbVk82xfP_Ok0JEqAT-FeLWJvpFYSOBbaSkMhCMnra5MgtfhWFrPWqHlhL2urT6atbU-oa0PNE8WXFFJ0-nazXakRroddGk9IwYEUnCd5w7Pddr5UTT8ZuVJY5F0fM7ebRLYyXNDgUnprJWxM-9lb7xAQLHe-M2xDYQCD9pD_2hez_kVn-P_rjLq6n3qjYv2iO5qz9DyvPdyv1ETp5eTTJ_7BGvQq8v1TVtl5jXUcRRcrqFh-dI4VtFlBN6t_ynLNkh5JpUmZEm5rbvfhkLiN6H4BQt2jYGYZklC_uzxWWJxsNCfUmkL2SJEJZuWdYs4cKaERS3IXlUJZNI_lGv7cxj2SMf2CeMx5_wBcbK19)
|
||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
This is only a brief overview of how the Virtual Dom works. There are several aspects not yet covered in this guide including how the Virtual Dom handles async-components, keyed diffing, and how it uses [bump allocation](https://github.com/fitzgen/bumpalo) to efficiently allocate VNodes. If need more information about the Virtual Dom, you can read the code of the [core](https://github.com/DioxusLabs/dioxus/tree/master/packages/core) crate or reach out to us on [Discord](https://discord.gg/XgGxMSkvUM).
|
This is only a brief overview of how the Virtual Dom works. There are several aspects not yet covered in this guide including:
|
||||||
|
|
||||||
|
* How the Virtual DOM handles async-components
|
||||||
|
* Keyed diffing
|
||||||
|
* Using [bump allocation](https://github.com/fitzgen/bumpalo) to efficiently allocate VNodes.
|
||||||
|
|
||||||
|
If you need more information about the Virtual Dom, you can read the code of the [core](https://github.com/DioxusLabs/dioxus/tree/master/packages/core) crate or reach out to us on [Discord](https://discord.gg/XgGxMSkvUM).
|
|
@ -76,7 +76,7 @@ Next, we need to modify our `main.rs` to use either hydrate on the client or ren
|
||||||
{{#include ../../../examples/hydration.rs}}
|
{{#include ../../../examples/hydration.rs}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. You should see the same page as before, but now you can interact with the buttons!
|
Now, build your client-side bundle with `dx build --features web` and run your server with `cargo run --features ssr`. You should see the same page as before, but now you can interact with the buttons!
|
||||||
|
|
||||||
## Sycronizing props between the server and client
|
## Sycronizing props between the server and client
|
||||||
|
|
||||||
|
@ -99,4 +99,4 @@ The only thing we need to change on the client is the props. `dioxus-fullstack`
|
||||||
{{#include ../../../examples/hydration_props.rs}}
|
{{#include ../../../examples/hydration_props.rs}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. Navigate to `http://localhost:8080/1` and you should see the counter start at 1. Navigate to `http://localhost:8080/2` and you should see the counter start at 2.
|
Now, build your client-side bundle with `dx build --features web` and run your server with `cargo run --features ssr`. Navigate to `http://localhost:8080/1` and you should see the counter start at 1. Navigate to `http://localhost:8080/2` and you should see the counter start at 2.
|
||||||
|
|
|
@ -24,7 +24,7 @@ Next, add the server function to your `main.rs`:
|
||||||
{{#include ../../../examples/server_function.rs}}
|
{{#include ../../../examples/server_function.rs}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. You should see a new button that multiplies the count by 2.
|
Now, build your client-side bundle with `dx build --features web` and run your server with `cargo run --features ssr`. You should see a new button that multiplies the count by 2.
|
||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ Hot reloading is automatically enabled when using the web renderer on debug buil
|
||||||
1. Run:
|
1. Run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dioxus serve --hot-reload
|
dx serve --hot-reload
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Change some code within a rsx or render macro
|
2. Change some code within a rsx or render macro
|
||||||
|
|
|
@ -59,5 +59,5 @@ Edit your `main.rs`:
|
||||||
And to serve our app:
|
And to serve our app:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dioxus serve
|
dx serve
|
||||||
```
|
```
|
||||||
|
|
|
@ -2,15 +2,16 @@
|
||||||
|
|
||||||
So far our components have had no state like a normal rust functions. However, in a UI component, it is often useful to have stateful functionality to build user interactions. For example, you might want to track whether the user has opened a drop-down, and render different things accordingly.
|
So far our components have had no state like a normal rust functions. However, in a UI component, it is often useful to have stateful functionality to build user interactions. For example, you might want to track whether the user has opened a drop-down, and render different things accordingly.
|
||||||
|
|
||||||
Hooks allow us to create state in our components. Hooks are Rust functions that take a reference to `ScopeState` (in a component, you can pass `cx`), and provide you with functionality and state.
|
Hooks allow us to create state in our components. Hooks are Rust functions that take a reference to [`ScopeState`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html) (in a component, you can pass `cx`), and provide you with functionality and state.
|
||||||
|
|
||||||
## `use_state` Hook
|
## `use_state` Hook
|
||||||
|
|
||||||
[`use_state`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_state.html) is one of the simplest hooks.
|
[`use_state`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_state.html) is one of the simplest hooks.
|
||||||
|
|
||||||
- You provide a closure that determines the initial value
|
- You provide a closure that determines the initial value: `let mut count = use_state(cx, || 0);`
|
||||||
- `use_state` gives you the current value, and a way to update it by setting it to something else
|
- `use_state` gives you the current value, and a way to update it by setting it to something else
|
||||||
- When the value updates, `use_state` makes the component re-render, and provides you with the new value
|
- When the value updates, `use_state` makes the component re-render (along with any other component
|
||||||
|
that references it), and then provides you with the new value.
|
||||||
|
|
||||||
For example, you might have seen the counter example, in which state (a number) is tracked using the `use_state` hook:
|
For example, you might have seen the counter example, in which state (a number) is tracked using the `use_state` hook:
|
||||||
|
|
||||||
|
@ -45,10 +46,11 @@ But how can Dioxus differentiate between multiple hooks in the same component? A
|
||||||
This is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. Because the order you call hooks matters, you must follow certain rules when using hooks:
|
This is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. Because the order you call hooks matters, you must follow certain rules when using hooks:
|
||||||
|
|
||||||
1. Hooks may be only used in components or other hooks (we'll get to that later)
|
1. Hooks may be only used in components or other hooks (we'll get to that later)
|
||||||
2. On every call to the component function
|
2. On every call to a component function
|
||||||
1. The same hooks must be called (except in the case of early returns, as explained later in the [Error Handling chapter](../best_practices/error_handling.md))
|
1. The same hooks must be called (except in the case of early returns, as explained later in the [Error Handling chapter](../best_practices/error_handling.md))
|
||||||
2. In the same order
|
2. In the same order
|
||||||
3. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions
|
3. Hook names should start with `use_` so you don't accidentally confuse them with regular
|
||||||
|
functions (`use_state()`, `use_ref()`, `use_future()`, etc...)
|
||||||
|
|
||||||
These rules mean that there are certain things you can't do with hooks:
|
These rules mean that there are certain things you can't do with hooks:
|
||||||
|
|
||||||
|
@ -74,9 +76,12 @@ These rules mean that there are certain things you can't do with hooks:
|
||||||
|
|
||||||
`use_state` is great for tracking simple values. However, you may notice in the [`UseState` API](https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html) that the only way to modify its value is to replace it with something else (e.g., by calling `set`, or through one of the `+=`, `-=` operators). This works well when it is cheap to construct a value (such as any primitive). But what if you want to maintain more complex data in the components state?
|
`use_state` is great for tracking simple values. However, you may notice in the [`UseState` API](https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html) that the only way to modify its value is to replace it with something else (e.g., by calling `set`, or through one of the `+=`, `-=` operators). This works well when it is cheap to construct a value (such as any primitive). But what if you want to maintain more complex data in the components state?
|
||||||
|
|
||||||
For example, suppose we want to maintain a `Vec` of values. If we stored it with `use_state`, the only way to add a new value to the list would be to create a new `Vec` with the additional value, and put it in the state. This is expensive! We want to modify the existing `Vec` instead.
|
For example, suppose we want to maintain a `Vec` of values. If we stored it with `use_state`, the
|
||||||
|
only way to add a new value to the list would be to copy the existing `Vec`, add our value to it,
|
||||||
|
and then replace the existing `Vec` in the state with it. This is expensive! We want to modify the
|
||||||
|
existing `Vec` instead.
|
||||||
|
|
||||||
Thankfully, there is another hook for that, `use_ref`! It is similar to `use_state`, but it lets you get a mutable reference to the contained data.
|
Thankfully, there is another hook for that, `use_ref`! It **is** similar to `use_state`, but it lets you get a mutable reference to the contained data.
|
||||||
|
|
||||||
Here's a simple example that keeps a list of events in a `use_ref`. We can acquire write access to the state with `.with_mut()`, and then just `.push` a new value to the state:
|
Here's a simple example that keeps a list of events in a `use_ref`. We can acquire write access to the state with `.with_mut()`, and then just `.push` a new value to the state:
|
||||||
|
|
||||||
|
@ -84,4 +89,18 @@ Here's a simple example that keeps a list of events in a `use_ref`. We can acqui
|
||||||
{{#include ../../../examples/hooks_use_ref.rs:component}}
|
{{#include ../../../examples/hooks_use_ref.rs:component}}
|
||||||
```
|
```
|
||||||
|
|
||||||
> The return values of `use_state` and `use_ref` (`UseState` and `UseRef`, respectively) are in some ways similar to [`Cell`](https://doc.rust-lang.org/std/cell/) and [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) – they provide interior mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered whenever you change the state.
|
> The return values of `use_state` and `use_ref` (
|
||||||
|
> [`UseState`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html) and
|
||||||
|
> [`UseRef`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseRef.html), respectively) are in
|
||||||
|
> some ways similar to [`Cell`](https://doc.rust-lang.org/std/cell/) and
|
||||||
|
> [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) – they provide interior
|
||||||
|
> mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered
|
||||||
|
> whenever you change the state.
|
||||||
|
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- [**dioxus_hooks** ](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/) rustdoc
|
||||||
|
- Documents all hook types included with dioxus by default Most of these are also covered in
|
||||||
|
later chapters of this guide.
|
||||||
|
- [Hooks Package](https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks)
|
||||||
|
|
19
docs/guide/src/en/interactivity/memoization.md
Normal file
19
docs/guide/src/en/interactivity/memoization.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Memoization
|
||||||
|
|
||||||
|
[`use_memo`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_memo.html) let's you memorize values and thus save computation time. This is useful for expensive calculations.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
#[inline_props]
|
||||||
|
fn Calculator(cx: Scope, number: usize) -> Element {
|
||||||
|
let bigger_number = use_memo(cx, (number,), |(number,)| {
|
||||||
|
// This will only be calculated when `number` has changed.
|
||||||
|
number * 100
|
||||||
|
});
|
||||||
|
render!(
|
||||||
|
p { "{bigger_number}" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
render!(Calculator { number: 0 })
|
||||||
|
}
|
||||||
|
```
|
|
@ -32,7 +32,7 @@ Finally, a third component will render the other two as children. It will be res
|
||||||
|
|
||||||
![Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: "me waiting for a language feature"](./images/meme_editor_screenshot.png)
|
![Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: "me waiting for a language feature"](./images/meme_editor_screenshot.png)
|
||||||
|
|
||||||
## Using Context
|
## Using Shared State
|
||||||
|
|
||||||
Sometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient.
|
Sometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient.
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ Suppose now that we want to implement a dark mode toggle for our app. To achieve
|
||||||
|
|
||||||
Now, we could write another `use_state` in the top component, and pass `is_dark_mode` down to every component through props. But think about what will happen as the app grows in complexity – almost every component that renders any CSS is going to need to know if dark mode is enabled or not – so they'll all need the same dark mode prop. And every parent component will need to pass it down to them. Imagine how messy and verbose that would get, especially if we had components several levels deep!
|
Now, we could write another `use_state` in the top component, and pass `is_dark_mode` down to every component through props. But think about what will happen as the app grows in complexity – almost every component that renders any CSS is going to need to know if dark mode is enabled or not – so they'll all need the same dark mode prop. And every parent component will need to pass it down to them. Imagine how messy and verbose that would get, especially if we had components several levels deep!
|
||||||
|
|
||||||
Dioxus offers a better solution than this "prop drilling" – providing context. The [`use_context_provider`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) hook is similar to `use_ref`, but it makes it available through [`use_context`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) for all children components.
|
Dioxus offers a better solution than this "prop drilling" – providing context. The [`use_shared_state_provider`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_shared_state_provider.html) hook is similar to `use_ref`, but it makes it available through [`use_shared_state`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_shared_state.html) for all children components.
|
||||||
|
|
||||||
First, we have to create a struct for our dark mode configuration:
|
First, we have to create a struct for our dark mode configuration:
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ As a result, any child component of `App` (direct or not), can access the `DarkM
|
||||||
{{#include ../../../examples/meme_editor_dark_mode.rs:use_context}}
|
{{#include ../../../examples/meme_editor_dark_mode.rs:use_context}}
|
||||||
```
|
```
|
||||||
|
|
||||||
> `use_context` returns `Option<UseSharedState<DarkMode>>` here. If the context has been provided, the value is `Some(UseSharedState<DarkMode>)`, which you can call `.read` or `.write` on, similarly to `UseRef`. Otherwise, the value is `None`.
|
> `use_shared_state` returns `Option<UseSharedState<DarkMode>>` here. If the context has been provided, the value is `Some(UseSharedState<DarkMode>)`, which you can call `.read` or `.write` on, similarly to `UseRef`. Otherwise, the value is `None`.
|
||||||
|
|
||||||
For example, here's how we would implement the dark mode toggle, which both reads the context (to determine what color it should render) and writes to it (to toggle dark mode):
|
For example, here's how we would implement the dark mode toggle, which both reads the context (to determine what color it should render) and writes to it (to toggle dark mode):
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,7 @@ async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
|
||||||
Podemos combinar corrotinas com `Fermi` para emular o sistema `Thunk` do **Redux Toolkit** com muito menos dor de cabeça. Isso nos permite armazenar todo o estado do nosso aplicativo _dentro_ de uma tarefa e, em seguida, simplesmente atualizar os valores de "visualização" armazenados em `Atoms`. Não pode ser subestimado o quão poderosa é essa técnica: temos todas as vantagens das tarefas nativas do Rust com as otimizações e ergonomia do estado global. Isso significa que seu estado _real_ não precisa estar vinculado a um sistema como `Fermi` ou `Redux` – os únicos `Atoms` que precisam existir são aqueles que são usados para controlar a interface.
|
Podemos combinar corrotinas com `Fermi` para emular o sistema `Thunk` do **Redux Toolkit** com muito menos dor de cabeça. Isso nos permite armazenar todo o estado do nosso aplicativo _dentro_ de uma tarefa e, em seguida, simplesmente atualizar os valores de "visualização" armazenados em `Atoms`. Não pode ser subestimado o quão poderosa é essa técnica: temos todas as vantagens das tarefas nativas do Rust com as otimizações e ergonomia do estado global. Isso significa que seu estado _real_ não precisa estar vinculado a um sistema como `Fermi` ou `Redux` – os únicos `Atoms` que precisam existir são aqueles que são usados para controlar a interface.
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
static USERNAME: Atom<String> = |_| "default".to_string();
|
static USERNAME: Atom<String> = Atom(|_| "default".to_string());
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let atoms = use_atom_root(cx);
|
let atoms = use_atom_root(cx);
|
||||||
|
@ -118,7 +118,7 @@ fn app(cx: Scope) -> Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Banner(cx: Scope) -> Element {
|
fn Banner(cx: Scope) -> Element {
|
||||||
let username = use_read(cx, USERNAME);
|
let username = use_read(cx, &USERNAME);
|
||||||
|
|
||||||
cx.render(rsx!{
|
cx.render(rsx!{
|
||||||
h1 { "Welcome back, {username}" }
|
h1 { "Welcome back, {username}" }
|
||||||
|
@ -134,8 +134,8 @@ enum SyncAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
|
async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
|
||||||
let username = atoms.write(USERNAME);
|
let username = atoms.write(&USERNAME);
|
||||||
let errors = atoms.write(ERRORS);
|
let errors = atoms.write(&ERRORS);
|
||||||
|
|
||||||
while let Ok(msg) = rx.next().await {
|
while let Ok(msg) = rx.next().await {
|
||||||
match msg {
|
match msg {
|
||||||
|
|
|
@ -113,14 +113,14 @@ enum InputError {
|
||||||
TooShort,
|
TooShort,
|
||||||
}
|
}
|
||||||
|
|
||||||
static INPUT_ERROR: Atom<InputError> = |_| InputError::None;
|
static INPUT_ERROR: Atom<InputError> = Atom(|_| InputError::None);
|
||||||
```
|
```
|
||||||
|
|
||||||
Então, em nosso componente de nível superior, queremos tratar explicitamente o possível estado de erro para esta parte da árvore.
|
Então, em nosso componente de nível superior, queremos tratar explicitamente o possível estado de erro para esta parte da árvore.
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
fn TopLevel(cx: Scope) -> Element {
|
fn TopLevel(cx: Scope) -> Element {
|
||||||
let error = use_read(cx, INPUT_ERROR);
|
let error = use_read(cx, &INPUT_ERROR);
|
||||||
|
|
||||||
match error {
|
match error {
|
||||||
TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
|
TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
|
||||||
|
@ -134,7 +134,7 @@ Agora, sempre que um componente _downstream_ tiver um erro em suas ações, ele
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
fn Commandline(cx: Scope) -> Element {
|
fn Commandline(cx: Scope) -> Element {
|
||||||
let set_error = use_set(cx, INPUT_ERROR);
|
let set_error = use_set(cx, &INPUT_ERROR);
|
||||||
|
|
||||||
cx.render(rsx!{
|
cx.render(rsx!{
|
||||||
input {
|
input {
|
||||||
|
|
|
@ -18,7 +18,7 @@ dioxus = { version = "*", features = ["hot-reload"] }
|
||||||
1. Execute:
|
1. Execute:
|
||||||
|
|
||||||
```
|
```
|
||||||
dioxus serve --hot-reload
|
dx serve --hot-reload
|
||||||
```
|
```
|
||||||
|
|
||||||
2. alterar algum código dentro de uma macro `rsx`
|
2. alterar algum código dentro de uma macro `rsx`
|
||||||
|
|
1
docs/reference/.gitignore
vendored
1
docs/reference/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
book
|
|
|
@ -1,25 +0,0 @@
|
||||||
# Dioxus: Guias Avançados e Referência
|
|
||||||
|
|
||||||
![dioxuslogo](./images/dioxuslogo_full.png)
|
|
||||||
|
|
||||||
**Dioxus** é um framework e ecossistema para desenvolver interfaces rápidas, escaláveis e robustas com a linguagem de Programação Rust. Este guia irá ajudar você a começar com o Dioxus para Web, Desktop, Móvel e mais.
|
|
||||||
|
|
||||||
> Este livro é a Referência e Guias Avançados para o framework Dioxus. Para um tutorial em como de fato _usar_ o Dioxus, procure o [guia oficial](https://dioxuslabs.com/guide/en/).
|
|
||||||
|
|
||||||
## Guias e Referência
|
|
||||||
|
|
||||||
Com a referência nós procuramos manter a documentar a funcionalidade que pode não ter sido mencionada no guia oficial para manter uma carga de informação mínima. Alguns tópicos não estão inclusos pelo guia, mas discutidos nesta referência incluindo:
|
|
||||||
|
|
||||||
- Processo seguro (`ThreadSafe`) da `VirtualDOM`
|
|
||||||
- Abordagem complete sobre o uso do `rsx!` e funções inclusas
|
|
||||||
- Padrão `spread` para as propriedades dos componentes
|
|
||||||
- Testes
|
|
||||||
- Memoization à fundo
|
|
||||||
- Elementos personalizados
|
|
||||||
- Renderizadores personalizados
|
|
||||||
|
|
||||||
## Contribuindo
|
|
||||||
|
|
||||||
Se nesse documento estiver de algum forma confuso, contém erros de digitação ou você gostaria de ajudar a melhorar algo, sinta-se à vontade para fazer um PR no [repositório do Dioxus](https://github.com/DioxusLabs/dioxus/tree/master/docs/reference).
|
|
||||||
|
|
||||||
Todas as contribuições serão licenciadas sob a licença MIT/Apache2.
|
|
|
@ -1,47 +0,0 @@
|
||||||
# Summary
|
|
||||||
|
|
||||||
- [Introdução](README.md)
|
|
||||||
|
|
||||||
- [Platformas](platforms/index.md)
|
|
||||||
|
|
||||||
- [Web](platforms/web.md)
|
|
||||||
- [Renderização por Servidor(SSR)](platforms/ssr.md)
|
|
||||||
- [Desktop](platforms/desktop.md)
|
|
||||||
- [Móvel](platforms/mobile.md)
|
|
||||||
- [TUI](platforms/tui.md)
|
|
||||||
|
|
||||||
- [Guias Avançados](guide/index.md)
|
|
||||||
|
|
||||||
- [RSX à fundo](guide/rsx_in_depth.md)
|
|
||||||
- [Componentes](guide/components.md)
|
|
||||||
- [Propriedades](guide/props.md)
|
|
||||||
- [Memoization](guide/memoization.md)
|
|
||||||
- [Desempenho](guide/performance.md)
|
|
||||||
- [Testes](guide/testing.md)
|
|
||||||
- [Construindo Elementos com o NodeFactory](guide/rsx.md)
|
|
||||||
- [Elementos Personalizados](guide/custom_elements.md)
|
|
||||||
- [Renderizadores Personalizados](guide/custom_renderer.md)
|
|
||||||
- [Componentes Renderizados por Servidor](guide/server_side_components.md)
|
|
||||||
- [Empacotando e Distribuindo](guide/bundline.md)
|
|
||||||
- [Recarregamento em Tempo-Real com RSX](guide/hot_reloading.md)
|
|
||||||
|
|
||||||
- [Guia de Referência](reference/reference.md)
|
|
||||||
- [Anti-padrões](reference/anti.md)
|
|
||||||
- [Filhos](reference/children.md)
|
|
||||||
- [Renderização Condicional](reference/conditional.md)
|
|
||||||
- [Entradas Controladas](reference/controlled.md)
|
|
||||||
- [Elementos Personalizados](reference/custom.md)
|
|
||||||
- [Componentes Vazios](reference/empty.md)
|
|
||||||
- [Tratamento de Errors](reference/error.md)
|
|
||||||
- [Fragmentos](reference/fragments.md)
|
|
||||||
- [CSS Globais](reference/global.md)
|
|
||||||
- [Estilos em Linha](reference/inline.md)
|
|
||||||
- [Iteradores](reference/iterators.md)
|
|
||||||
- [Ouvintes](reference/listeners.md)
|
|
||||||
- [Memoization](reference/memoization.md)
|
|
||||||
- [Nós de Referência](reference/node.md)
|
|
||||||
- [Padrão Propagado (Spread)](reference/spread.md)
|
|
||||||
- [Gerenciamento de Estado](reference/state.md)
|
|
||||||
- [Suspensão](reference/suspense.md)
|
|
||||||
- [Tarefas](reference/task.md)
|
|
||||||
- [Testes](reference/testing.md)
|
|
|
@ -1,497 +0,0 @@
|
||||||
# Renderizador Personalizado
|
|
||||||
|
|
||||||
Dioxus é uma estrutura incrivelmente portátil para desenvolvimento de interface do usuário. As lições, conhecimentos, hooks e componentes que você adquire ao longo do tempo sempre podem ser aproveitados para projetos futuros. No entanto, às vezes, esses projetos não podem aproveitar um renderizador compatível ou você precisa implementar seu próprio renderizador melhor.
|
|
||||||
|
|
||||||
Ótimas notícias: o design do renderizador depende inteiramente de você! Nós fornecemos sugestões e inspiração com os renderizadores originais, mas só realmente precisamos processar `DomEdits` e enviar `UserEvents`.
|
|
||||||
|
|
||||||
## Em Detalhes:
|
|
||||||
|
|
||||||
A implementação do renderizador é bastante simples. O renderizador precisa:
|
|
||||||
|
|
||||||
1. Lidar com o fluxo de edições gerado por atualizações no DOM virtual
|
|
||||||
2. Registrar ouvintes e passe eventos para o sistema de eventos do DOM virtual
|
|
||||||
|
|
||||||
Essencialmente, seu renderizador precisa implementar o traço `RealDom` e gerar objetos `EventTrigger` para atualizar o `VirtualDOM`. A partir daí, você terá tudo o que precisa para renderizar o `VirtualDOM` na tela.
|
|
||||||
|
|
||||||
Internamente, o Dioxus lida com o relacionamento da árvore, `diffing`, gerenciamento de memória e o sistema de eventos, deixando o mínimo necessário para que os renderizadores se implementem.
|
|
||||||
|
|
||||||
Para referência, confira o interpretador JavaScript ou o renderizador TUI como ponto de partida para seu renderizador personalizado.
|
|
||||||
|
|
||||||
## DomEdições
|
|
||||||
|
|
||||||
O tipo "DomEdit" é uma enumeração serializada que representa uma operação atômica que ocorre no `RealDom`. As variantes seguem aproximadamente este conjunto:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
enum DomEdit {
|
|
||||||
PushRoot,
|
|
||||||
AppendChildren,
|
|
||||||
ReplaceWith,
|
|
||||||
InsertAfter,
|
|
||||||
InsertBefore,
|
|
||||||
Remove,
|
|
||||||
CreateTextNode,
|
|
||||||
CreateElement,
|
|
||||||
CreateElementNs,
|
|
||||||
CreatePlaceholder,
|
|
||||||
NewEventListener,
|
|
||||||
RemoveEventListener,
|
|
||||||
SetText,
|
|
||||||
SetAttribute,
|
|
||||||
RemoveAttribute,
|
|
||||||
PopRoot,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
O mecanismo de diferenciação do Dioxus opera como uma [máquina de pilha] (https://en.wikipedia.org/wiki/Stack_machine) onde o método `push_root` empilhar um novo nó DOM "real" para a pilha e `append_child` e `replace_with` ambos removem nós da pilha.
|
|
||||||
|
|
||||||
### Um exemplo
|
|
||||||
|
|
||||||
Por uma questão de compreensão, vamos considerar este exemplo - uma declaração de interface do usuário muito simples:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
rsx!( h1 {"hello world"} )
|
|
||||||
```
|
|
||||||
|
|
||||||
To get things started, Dioxus must first navigate to the container of this h1 tag. To "navigate" here, the internal diffing algorithm generates the DomEdit `PushRoot` where the ID of the root is the container.
|
|
||||||
|
|
||||||
When the renderer receives this instruction, it pushes the actual Node onto its own stack. The real renderer's stack will look like this:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
instructions: [
|
|
||||||
PushRoot(Container)
|
|
||||||
]
|
|
||||||
stack: [
|
|
||||||
ContainerNode,
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Em seguida, o Dioxus encontrará o nó `h1`. O algoritmo `diff` decide que este nó precisa ser criado, então o Dioxus irá gerar o DomEdit `CreateElement`. Quando o renderizador receber esta instrução, ele criará um nó desmontado e o enviará para sua própria pilha:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
instructions: [
|
|
||||||
PushRoot(Container),
|
|
||||||
CreateElement(h1),
|
|
||||||
]
|
|
||||||
stack: [
|
|
||||||
ContainerNode,
|
|
||||||
h1,
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Em seguida, Dioxus vê o nó de texto e gera o DomEdit `CreateTextNode`:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
instructions: [
|
|
||||||
PushRoot(Container),
|
|
||||||
CreateElement(h1),
|
|
||||||
CreateTextNode("hello world")
|
|
||||||
]
|
|
||||||
stack: [
|
|
||||||
ContainerNode,
|
|
||||||
h1,
|
|
||||||
"hello world"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Lembre-se, o nó de texto não está anexado a nada (ele está desmontado), então o Dioxus precisa gerar um `Edit` que conecte o nó de texto ao elemento `h1`. Depende da situação, mas neste caso usamos `AppendChildren`. Isso remove o nó de texto da pilha, deixando o elemento `h1` como o próximo elemento na linha.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
instructions: [
|
|
||||||
PushRoot(Container),
|
|
||||||
CreateElement(h1),
|
|
||||||
CreateTextNode("hello world"),
|
|
||||||
AppendChildren(1)
|
|
||||||
]
|
|
||||||
stack: [
|
|
||||||
ContainerNode,
|
|
||||||
h1
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Chamamos `AppendChildren` novamente, retirando o nó `h1` e anexando-o ao pai:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
instructions: [
|
|
||||||
PushRoot(Container),
|
|
||||||
CreateElement(h1),
|
|
||||||
CreateTextNode("hello world"),
|
|
||||||
AppendChildren(1),
|
|
||||||
AppendChildren(1)
|
|
||||||
]
|
|
||||||
stack: [
|
|
||||||
ContainerNode,
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Finalmente, o contêiner é aberto, pois não precisamos mais dele.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
instructions: [
|
|
||||||
PushRoot(Container),
|
|
||||||
CreateElement(h1),
|
|
||||||
CreateTextNode("hello world"),
|
|
||||||
AppendChildren(1),
|
|
||||||
AppendChildren(1),
|
|
||||||
PopRoot
|
|
||||||
]
|
|
||||||
stack: []
|
|
||||||
```
|
|
||||||
|
|
||||||
Com o tempo, nossa pilha ficou assim:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
[]
|
|
||||||
[Container]
|
|
||||||
[Container, h1]
|
|
||||||
[Container, h1, "hello world"]
|
|
||||||
[Container, h1]
|
|
||||||
[Container]
|
|
||||||
[]
|
|
||||||
```
|
|
||||||
|
|
||||||
Observe como nossa pilha fica vazia depois que a interface do usuário é montada. Convenientemente, essa abordagem separa completamente o `VirtualDOM` e o `RealDOM`. Além disso, essas edições são serializáveis, o que significa que podemos até gerenciar UIs em uma conexão de rede. Esta pequena máquina de pilha e edições serializadas tornam o Dioxus independente das especificidades da plataforma.
|
|
||||||
|
|
||||||
Dioxus também é muito rápido. Como o Dioxus divide a fase de `diff` e `patch`, ele é capaz de fazer todas as edições no `RealDOM` em um período de tempo muito curto (menos de um único quadro), tornando a renderização muito rápida. Ele também permite que o Dioxus cancele grandes operações de diferenciação se ocorrer um trabalho de prioridade mais alta durante a diferenciação.
|
|
||||||
|
|
||||||
É importante notar que há uma camada de conexão entre o Dioxus e o renderizador. O Dioxus salva e carrega elementos (a edição `PushRoot`) com um ID. Dentro do `VirtualDOM`, isso é rastreado apenas como um `u64`.
|
|
||||||
|
|
||||||
Sempre que uma edição `CreateElement` é gerada durante a comparação, o Dioxus incrementa seu contador de nós e atribui a esse novo elemento seu `NodeCount` atual. O `RealDom` é responsável por lembrar este ID e enviar o nó correto quando `PushRoot(ID)` é gerado. Dioxus recupera os IDs de elementos quando removidos. Para ficar em sincronia com Dioxus, você pode usar um `Sparce Vec` (`Vec<Option<T>>`) com itens possivelmente desocupados. Você pode usar os ids como índices no `Vec` para elementos e aumentar o `Vec` quando um id não existir.
|
|
||||||
|
|
||||||
Esta pequena demonstração serve para mostrar exatamente como um renderizador precisaria processar um stream de edição para construir UIs. Um conjunto de DOMEdits serializados para várias demos está disponível para você testar seu renderizador personalizado.
|
|
||||||
|
|
||||||
## Ciclo de eventos
|
|
||||||
|
|
||||||
Como a maioria das GUIs, o Dioxus conta com um `loop` de eventos para progredir no `VirtualDOM`. O próprio `VirtualDOM` também pode produzir eventos, por isso é importante que seu renderizador personalizado também possa lidar com eles.
|
|
||||||
|
|
||||||
O código para a implementação do `WebSys` é direto, então vamos adicioná-lo aqui para demonstrar como um `loop` de eventos é simples:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
|
|
||||||
// Push the body element onto the WebsysDom's stack machine
|
|
||||||
let mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());
|
|
||||||
websys_dom.stack.push(root_node);
|
|
||||||
|
|
||||||
// Rebuild or hydrate the virtualdom
|
|
||||||
let mutations = self.internal_dom.rebuild();
|
|
||||||
websys_dom.apply_mutations(mutations);
|
|
||||||
|
|
||||||
// Wait for updates from the real dom and progress the virtual dom
|
|
||||||
loop {
|
|
||||||
let user_input_future = websys_dom.wait_for_event();
|
|
||||||
let internal_event_future = self.internal_dom.wait_for_work();
|
|
||||||
|
|
||||||
match select(user_input_future, internal_event_future).await {
|
|
||||||
Either::Left((_, _)) => {
|
|
||||||
let mutations = self.internal_dom.work_with_deadline(|| false);
|
|
||||||
websys_dom.apply_mutations(mutations);
|
|
||||||
},
|
|
||||||
Either::Right((event, _)) => websys_dom.handle_event(event),
|
|
||||||
}
|
|
||||||
|
|
||||||
// render
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
É importante que você decodifique os eventos reais do seu sistema de eventos no sistema de eventos sintético do Dioxus (significado sintético abstraído). Isso significa simplesmente combinar seu tipo de evento e criar um tipo Dioxus `UserEvent`. No momento, o sistema `VirtualEvent` é modelado quase inteiramente em torno da especificação HTML, mas estamos interessados em reduzi-lo.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
|
|
||||||
match event.type_().as_str() {
|
|
||||||
"keydown" => {
|
|
||||||
let event: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();
|
|
||||||
UserEvent::KeyboardEvent(UserEvent {
|
|
||||||
scope_id: None,
|
|
||||||
priority: EventPriority::Medium,
|
|
||||||
name: "keydown",
|
|
||||||
// This should be whatever element is focused
|
|
||||||
element: Some(ElementId(0)),
|
|
||||||
data: Arc::new(KeyboardData{
|
|
||||||
char_code: event.char_code(),
|
|
||||||
key: event.key(),
|
|
||||||
key_code: event.key_code(),
|
|
||||||
alt_key: event.alt_key(),
|
|
||||||
ctrl_key: event.ctrl_key(),
|
|
||||||
meta_key: event.meta_key(),
|
|
||||||
shift_key: event.shift_key(),
|
|
||||||
locale: "".to_string(),
|
|
||||||
location: event.location(),
|
|
||||||
repeat: event.repeat(),
|
|
||||||
which: event.which(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Elementos brutos personalizados
|
|
||||||
|
|
||||||
Se você precisar ir mais longe a ponto de confiar em elementos personalizados para o seu renderizador - você pode. Isso ainda permitiria que você usasse a natureza reativa do Dioxus, sistema de componentes, estado compartilhado e outros recursos, mas acabará gerando nós diferentes. Todos os atributos e ouvintes para o namespace HTML e SVG são transportados por meio de estruturas auxiliares que essencialmente compilam (não representam sobrecarga de tempo de execução). Você pode colocar seus próprios elementos a qualquer hora que quiser, sem problemas. No entanto, você deve ter certeza absoluta de que seu renderizador pode lidar com o novo tipo, ou ele irá "bater e queimar".
|
|
||||||
|
|
||||||
Esses elementos personalizados são definidos como `unit struct` com implementações de `traits`.
|
|
||||||
|
|
||||||
Por exemplo, o elemento `div` é (aproximadamente!) definido assim:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
struct div;
|
|
||||||
impl div {
|
|
||||||
/// Some glorious documentation about the class property.
|
|
||||||
const TAG_NAME: &'static str = "div";
|
|
||||||
const NAME_SPACE: Option<&'static str> = None;
|
|
||||||
// define the class attribute
|
|
||||||
pub fn class<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
|
||||||
cx.attr("class", val, None, false)
|
|
||||||
}
|
|
||||||
// more attributes
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Você provavelmente notou que muitos elementos nas macros `rsx!` suportam documentação em foco. A abordagem que adotamos para elementos personalizados significa que a estrutura da unidade é criada imediatamente onde o elemento é usado no macro. Quando o macro é expandido, os comentários doc ainda se aplicam à estrutura da unidade, dando toneladas de feedback no editor, mesmo dentro de uma macro procedural.
|
|
||||||
|
|
||||||
# Núcleo Nativo
|
|
||||||
|
|
||||||
Os renderizadores dão muito trabalho. Se você estiver criando um renderizador em Rust, o núcleo nativo fornece alguns utilitários para implementar um renderizador. Ele fornece uma abstração sobre DomEdits e manipula o layout para você.
|
|
||||||
|
|
||||||
## RealDom
|
|
||||||
|
|
||||||
O `RealDom` é uma abstração de nível superior sobre a atualização do DOM. Ele é atualizado com `DomEdits` e fornece uma maneira de atualizar lentamente o estado dos nós com base em quais atributos mudam.
|
|
||||||
|
|
||||||
### Exemplo
|
|
||||||
|
|
||||||
Vamos construir um renderizador de brinquedo com bordas, tamanho e cor do texto.
|
|
||||||
Antes de começarmos, vamos dar uma olhada em um elemento de exemplo que podemos renderizar:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
cx.render(rsx!{
|
|
||||||
div{
|
|
||||||
color: "red",
|
|
||||||
p{
|
|
||||||
border: "1px solid black",
|
|
||||||
"hello world"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Nesta árvore a cor depende da cor do pai. O tamanho depende do tamanho das crianças, do texto atual e do tamanho do texto. A borda depende apenas do nó atual.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TB
|
|
||||||
subgraph context
|
|
||||||
text_width(text width)
|
|
||||||
end
|
|
||||||
subgraph div
|
|
||||||
state1(state)-->color1(color)
|
|
||||||
state1(state)-->border1(border)
|
|
||||||
border1-.->text_width
|
|
||||||
linkStyle 2 stroke:#5555ff,stroke-width:4px;
|
|
||||||
state1(state)-->layout_width1(layout width)
|
|
||||||
end
|
|
||||||
subgraph p
|
|
||||||
state2(state)-->color2(color)
|
|
||||||
color2-.->color1(color)
|
|
||||||
linkStyle 5 stroke:#0000ff,stroke-width:4px;
|
|
||||||
state2(state)-->border2(border)
|
|
||||||
border2-.->text_width
|
|
||||||
linkStyle 7 stroke:#5555ff,stroke-width:4px;
|
|
||||||
state2(state)-->layout_width2(layout width)
|
|
||||||
layout_width1-.->layout_width2
|
|
||||||
linkStyle 9 stroke:#aaaaff,stroke-width:4px;
|
|
||||||
end
|
|
||||||
subgraph hello world
|
|
||||||
state3(state)-->color3(color)
|
|
||||||
color3-.->color2(color)
|
|
||||||
linkStyle 11 stroke:#0000ff,stroke-width:4px;
|
|
||||||
state3(state)-->border3(border)
|
|
||||||
border3-.->text_width
|
|
||||||
linkStyle 13 stroke:#5555ff,stroke-width:4px;
|
|
||||||
state3(state)-->layout_width3(layout width)
|
|
||||||
layout_width2-.->layout_width3
|
|
||||||
linkStyle 15 stroke:#aaaaff,stroke-width:4px;
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Para ajudar na construção de um Dom, o núcleo nativo fornece quatro características: `State`, `ChildDepState`, `ParentDepState` e `NodeDepState` e uma estrutura `RealDom`.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
use dioxus_native_core::node_ref::*;
|
|
||||||
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
|
|
||||||
use dioxus_native_core_macro::{sorted_str_slice, State};
|
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone)]
|
|
||||||
struct Size(f32, f32);
|
|
||||||
// Size only depends on the current node and its children, so it implements ChildDepState
|
|
||||||
impl ChildDepState for Size {
|
|
||||||
// Size accepts a font size context
|
|
||||||
type Ctx = f32;
|
|
||||||
// Size depends on the Size part of each child
|
|
||||||
type DepState = Self;
|
|
||||||
// Size only cares about the width, height, and text parts of the current node
|
|
||||||
const NODE_MASK: NodeMask =
|
|
||||||
NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["width", "height"]))).with_text();
|
|
||||||
fn reduce<'a>(
|
|
||||||
&mut self,
|
|
||||||
node: NodeView,
|
|
||||||
children: impl Iterator<Item = &'a Self::DepState>,
|
|
||||||
ctx: &Self::Ctx,
|
|
||||||
) -> bool
|
|
||||||
where
|
|
||||||
Self::DepState: 'a,
|
|
||||||
{
|
|
||||||
let mut width;
|
|
||||||
let mut height;
|
|
||||||
if let Some(text) = node.text() {
|
|
||||||
// if the node has text, use the text to size our object
|
|
||||||
width = text.len() as f32 * ctx;
|
|
||||||
height = ctx;
|
|
||||||
} else {
|
|
||||||
// otherwise, the size is the maximum size of the children
|
|
||||||
width = *children
|
|
||||||
.reduce(|accum, item| if accum >= item.0 { accum } else { item.0 })
|
|
||||||
.unwrap_or(0.0));
|
|
||||||
height = *children
|
|
||||||
.reduce(|accum, item| if accum >= item.1 { accum } else { item.1 })
|
|
||||||
.unwrap_or(&0.0);
|
|
||||||
}
|
|
||||||
// if the node contains a width or height attribute it overrides the other size
|
|
||||||
for a in node.attibutes(){
|
|
||||||
match a.name{
|
|
||||||
"width" => width = a.value.parse().unwrap(),
|
|
||||||
"height" => height = a.value.parse().unwrap(),
|
|
||||||
// because Size only depends on the width and height, no other attributes will be passed to the member
|
|
||||||
_ => panic!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
|
|
||||||
let changed = (width != self.0) || (height != self.1);
|
|
||||||
*self = Self(width, height);
|
|
||||||
changed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
|
||||||
struct TextColor {
|
|
||||||
r: u8,
|
|
||||||
g: u8,
|
|
||||||
b: u8,
|
|
||||||
}
|
|
||||||
// TextColor only depends on the current node and its parent, so it implements ParentDepState
|
|
||||||
impl ParentDepState for TextColor {
|
|
||||||
type Ctx = ();
|
|
||||||
// TextColor depends on the TextColor part of the parent
|
|
||||||
type DepState = Self;
|
|
||||||
// TextColor only cares about the color attribute of the current node
|
|
||||||
const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["color"]));
|
|
||||||
fn reduce(
|
|
||||||
&mut self,
|
|
||||||
node: NodeView,
|
|
||||||
parent: Option<&Self::DepState>,
|
|
||||||
_ctx: &Self::Ctx,
|
|
||||||
) -> bool {
|
|
||||||
// TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
|
|
||||||
let new = match node.attributes().next() {
|
|
||||||
// if there is a color tag, translate it
|
|
||||||
Some("red") => TextColor { r: 255, g: 0, b: 0 },
|
|
||||||
Some("green") => TextColor { r: 0, g: 255, b: 0 },
|
|
||||||
Some("blue") => TextColor { r: 0, g: 0, b: 255 },
|
|
||||||
Some(_) => panic!("unknown color"),
|
|
||||||
// otherwise check if the node has a parent and inherit that color
|
|
||||||
None => match parent {
|
|
||||||
Some(parent) => *parent,
|
|
||||||
None => Self::default(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// check if the member has changed
|
|
||||||
let changed = new != *self;
|
|
||||||
*self = new;
|
|
||||||
changed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default)]
|
|
||||||
struct Border(bool);
|
|
||||||
// TextColor only depends on the current node, so it implements NodeDepState
|
|
||||||
impl NodeDepState for Border {
|
|
||||||
type Ctx = ();
|
|
||||||
// Border does not depended on any other member in the current node
|
|
||||||
type DepState = ();
|
|
||||||
// Border does not depended on any other member in the current node
|
|
||||||
const NODE_MASK: NodeMask =
|
|
||||||
NodeMask::new_with_attrs(AttributeMask::Static(&["border"]));
|
|
||||||
fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
|
|
||||||
// check if the node contians a border attribute
|
|
||||||
let new = Self(node.attributes().next().map(|a| a.name == "border").is_some());
|
|
||||||
// check if the member has changed
|
|
||||||
let changed = new != *self;
|
|
||||||
*self = new;
|
|
||||||
changed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// State provides a derive macro, but anotations on the members are needed in the form #[dep_type(dep_member, CtxType)]
|
|
||||||
#[derive(State, Default, Clone)]
|
|
||||||
struct ToyState {
|
|
||||||
// the color member of it's parent and no context
|
|
||||||
#[parent_dep_state(color)]
|
|
||||||
color: TextColor,
|
|
||||||
// depends on the node, and no context
|
|
||||||
#[node_dep_state()]
|
|
||||||
border: Border,
|
|
||||||
// depends on the layout_width member of children and f32 context (for text size)
|
|
||||||
#[child_dep_state(size, f32)]
|
|
||||||
size: Size,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Agora que temos nosso estado, podemos colocá-lo em uso em nosso DOM. Nós podemos atualizar o DOM com `update_state` para atualizar a estrutura do `DOM` (adicionando, removendo e alterando as propriedades dos nós) e então `apply_mutations` para atualizar o `ToyState` para cada um dos nós que foram alterados.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
fn main(){
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx!{
|
|
||||||
div{
|
|
||||||
color: "red",
|
|
||||||
"hello world"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
let vdom = VirtualDom::new(app);
|
|
||||||
let rdom: RealDom<ToyState> = RealDom::new();
|
|
||||||
|
|
||||||
let mutations = dom.rebuild();
|
|
||||||
// update the structure of the real_dom tree
|
|
||||||
let to_update = rdom.apply_mutations(vec![mutations]);
|
|
||||||
let mut ctx = AnyMap::new();
|
|
||||||
// set the font size to 3.3
|
|
||||||
ctx.insert(3.3);
|
|
||||||
// update the ToyState for nodes in the real_dom tree
|
|
||||||
let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap();
|
|
||||||
|
|
||||||
// we need to run the vdom in a async runtime
|
|
||||||
tokio::runtime::Builder::new_current_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()?
|
|
||||||
.block_on(async {
|
|
||||||
loop{
|
|
||||||
let wait = vdom.wait_for_work();
|
|
||||||
let mutations = vdom.work_with_deadline(|| false);
|
|
||||||
let to_update = rdom.apply_mutations(mutations);
|
|
||||||
let mut ctx = AnyMap::new();
|
|
||||||
ctx.insert(3.3);
|
|
||||||
let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap();
|
|
||||||
|
|
||||||
// render...
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Disposição
|
|
||||||
|
|
||||||
Para a maioria das plataformas, o layout dos Elementos permanecerá o mesmo. O módulo layout_attributes fornece uma maneira de aplicar atributos `html` a um estilo de layout estendido.
|
|
||||||
|
|
||||||
## Conclusão
|
|
||||||
|
|
||||||
Pronto, é isso! Você deve ter quase todo o conhecimento necessário sobre como implementar seu próprio renderizador. Estamos super interessados em ver os aplicativos Dioxus trazidos para renderizadores de desktop personalizados, renderizador para dispositivos móveis, interface do usuário para videogames e até realidade aumentada! Se você estiver interessado em contribuir para qualquer um desses projetos, não tenha medo de entrar em contato ou se juntar à comunidade.
|
|
|
@ -1,31 +0,0 @@
|
||||||
# Recarregamento a Quente
|
|
||||||
|
|
||||||
1. O recarregamento a quente permite tempos de iteração muito mais rápidos dentro de chamadas rsx, interpretando-as e transmitindo as edições.
|
|
||||||
2. É útil ao alterar o estilo/layout de um programa, mas não ajudará na alteração da lógica de um programa.
|
|
||||||
3. Atualmente, o cli implementa apenas o recarregamento a quente para o renderizador da web.
|
|
||||||
|
|
||||||
# Configurar
|
|
||||||
|
|
||||||
Instale o [dioxus-cli](https://github.com/DioxusLabs/cli).
|
|
||||||
Habilite o recurso hot_reload no dioxus:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
dioxus = { version = "*", features = ["web", "hot_reload"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
# Como Usar
|
|
||||||
|
|
||||||
1. run:
|
|
||||||
|
|
||||||
```
|
|
||||||
dioxus serve --hot-reload
|
|
||||||
```
|
|
||||||
|
|
||||||
2. alterar algum código dentro de uma macro rsx
|
|
||||||
3. abra seu localhost em um navegador
|
|
||||||
4. salve e observe a mudança de estilo sem recompilar
|
|
||||||
|
|
||||||
# Limitações
|
|
||||||
|
|
||||||
1. O intérprete só pode usar expressões que existiam na última recompilação completa. Se você introduzir uma nova variável ou expressão na chamada rsx, ela acionará uma recompilação completa para capturar a expressão.
|
|
||||||
2. Componentes e Iteradores podem conter código de Rust arbitrário e acionarão uma recompilação completa quando alterados.
|
|
|
@ -1 +0,0 @@
|
||||||
# Guias Avançados
|
|
|
@ -1,228 +0,0 @@
|
||||||
# RSX in Depth
|
|
||||||
|
|
||||||
The RSX macro makes it very easy to assemble complex UIs with a very natural Rust syntax:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
rsx!(
|
|
||||||
div {
|
|
||||||
button {
|
|
||||||
onclick: move |e| todos.write().new_todo(),
|
|
||||||
"Add todo"
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
class: "todo-list",
|
|
||||||
todos.iter().map(|(key, todo)| rsx!(
|
|
||||||
li {
|
|
||||||
class: "beautiful-todo"
|
|
||||||
key: "f"
|
|
||||||
h3 { "{todo.title}" }
|
|
||||||
p { "{todo.contents}"}
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
In this section, we'll cover the `rsx!` macro in depth. If you prefer to learn through examples, the `code reference` guide has plenty of examples on how to use `rsx!` effectively.
|
|
||||||
|
|
||||||
### Element structure
|
|
||||||
|
|
||||||
Attributes must come before child elements
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
div {
|
|
||||||
hidden: "false",
|
|
||||||
"some text"
|
|
||||||
child {}
|
|
||||||
Component {} // uppercase
|
|
||||||
component() // lowercase is treated like a function call
|
|
||||||
(0..10).map(|f| rsx!{ "hi {f}" }) // arbitrary rust expressions
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work:
|
|
||||||
|
|
||||||
- `name: value` sets a property on this element.
|
|
||||||
- `"text"` adds a new text element
|
|
||||||
- `tag {}` adds a new child element
|
|
||||||
- `CustomTag {}` adds a new child component
|
|
||||||
- `{expr}` pastes the `expr` tokens literally. They must be `IntoIterator<T> where T: IntoVnode` to work properly
|
|
||||||
|
|
||||||
Commas are entirely optional, but might be useful to delineate between elements and attributes.
|
|
||||||
|
|
||||||
The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it.
|
|
||||||
|
|
||||||
### Ignoring `cx.render` with `render!(...)`
|
|
||||||
|
|
||||||
Sometimes, writing `cx.render` is a hassle. The `rsx! macro will accept any token followed by a comma as the target to call "render" on:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
cx.render(rsx!( div {} ))
|
|
||||||
// becomes
|
|
||||||
render!(div {})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Conditional Rendering
|
|
||||||
|
|
||||||
Sometimes, you might not want to render an element given a condition. The rsx! macro will accept any tokens directly contained with curly braces, provided they resolve to a type that implements `IntoIterator<VNode>`. This lets us write any Rust expression that resolves to a VNode:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
rsx!({
|
|
||||||
if enabled {
|
|
||||||
render!(div {"enabled"})
|
|
||||||
} else {
|
|
||||||
render!(li {"disabled"})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
A convenient way of hiding/showing an element is returning an `Option<VNode>`. When combined with `and_then`, we can succinctly control the display state given some boolean:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
rsx!({
|
|
||||||
a.and_then(rsx!(div {"enabled"}))
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
It's important to note that the expression `rsx!()` is typically lazy - this expression must be _rendered_ to produce a VNode. When using match statements, we must render every arm as to avoid the `no two closures are identical` rule that Rust imposes:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
// this will not compile!
|
|
||||||
match case {
|
|
||||||
true => rsx!(div {}),
|
|
||||||
false => rsx!(div {})
|
|
||||||
}
|
|
||||||
|
|
||||||
// the nodes must be rendered first
|
|
||||||
match case {
|
|
||||||
true => render!(div {}),
|
|
||||||
false => render!(div {})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lists
|
|
||||||
|
|
||||||
Again, because anything that implements `IntoIterator<VNode>` is valid, we can use lists directly in our `rsx!`:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
let items = vec!["a", "b", "c"];
|
|
||||||
|
|
||||||
cx.render(rsx!{
|
|
||||||
ul {
|
|
||||||
{items.iter().map(|f| rsx!(li { "a" }))}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Sometimes, it makes sense to render VNodes into a list:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
let mut items = vec![];
|
|
||||||
|
|
||||||
for _ in 0..5 {
|
|
||||||
items.push(render!(li {} ))
|
|
||||||
}
|
|
||||||
|
|
||||||
render!({items} )
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Lists and Keys
|
|
||||||
|
|
||||||
When rendering the VirtualDom to the screen, Dioxus needs to know which elements have been added and which have been removed. These changes are determined through a process called "diffing" - an old set of elements is compared to a new set of elements. If an element is removed, then it won't show up in the new elements, and Dioxus knows to remove it.
|
|
||||||
|
|
||||||
However, with lists, Dioxus does not exactly know how to determine which elements have been added or removed if the order changes or if an element is added or removed from the middle of the list.
|
|
||||||
|
|
||||||
In these cases, it is vitally important to specify a "key" alongside the element. Keys should be persistent between renders.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
fn render_list(cx: Scope, items: HashMap<String, Todo>) -> DomTree {
|
|
||||||
render!(ul {
|
|
||||||
{items.iter().map(|key, item| {
|
|
||||||
li {
|
|
||||||
key: key,
|
|
||||||
h2 { "{todo.title}" }
|
|
||||||
p { "{todo.contents}" }
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
There have been many guides made for keys in React, so we recommend reading up to understand their importance:
|
|
||||||
|
|
||||||
- [React guide on keys](https://reactjs.org/docs/lists-and-keys.html)
|
|
||||||
- [Importance of keys (Medium)](https://kentcdodds.com/blog/understanding-reacts-key-prop)
|
|
||||||
|
|
||||||
### Complete Reference
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
let text = "example";
|
|
||||||
|
|
||||||
cx.render(rsx!{
|
|
||||||
div {
|
|
||||||
h1 { "Example" },
|
|
||||||
|
|
||||||
{title}
|
|
||||||
|
|
||||||
// fstring interpolation
|
|
||||||
"{text}"
|
|
||||||
|
|
||||||
p {
|
|
||||||
// Attributes
|
|
||||||
tag: "type",
|
|
||||||
|
|
||||||
// Anything that implements display can be an attribute
|
|
||||||
abc: 123,
|
|
||||||
|
|
||||||
enabled: true,
|
|
||||||
|
|
||||||
// attributes also supports interpolation
|
|
||||||
// `class` is not a restricted keyword unlike JS and ClassName
|
|
||||||
class: "big small wide short {text}",
|
|
||||||
|
|
||||||
class: format_args!("attributes take fmt::Arguments. {}", 99),
|
|
||||||
|
|
||||||
tag: {"these tokens are placed directly"}
|
|
||||||
|
|
||||||
// Children
|
|
||||||
a { "abcder" },
|
|
||||||
|
|
||||||
// Children with attributes
|
|
||||||
h2 { "hello", class: "abc-123" },
|
|
||||||
|
|
||||||
// Child components
|
|
||||||
CustomComponent { a: 123, b: 456, key: "1" },
|
|
||||||
|
|
||||||
// Child components with paths
|
|
||||||
crate::components::CustomComponent { a: 123, b: 456, key: "1" },
|
|
||||||
|
|
||||||
// Iterators
|
|
||||||
{ (0..3).map(|i| rsx!( h1 {"{:i}"} )) },
|
|
||||||
|
|
||||||
// More rsx!, or even html!
|
|
||||||
{ rsx! { div { } } },
|
|
||||||
{ html! { <div> </div> } },
|
|
||||||
|
|
||||||
// Matching
|
|
||||||
// Requires rendering the nodes first.
|
|
||||||
// rsx! is lazy, and the underlying closures cannot have the same type
|
|
||||||
// Rendering produces the VNode type
|
|
||||||
{match rand::gen_range::<i32>(1..3) {
|
|
||||||
1 => render!(h1 { "big" })
|
|
||||||
2 => render!(h2 { "medium" })
|
|
||||||
_ => render!(h3 { "small" })
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Optionals
|
|
||||||
{true.and_then(|f| rsx!( h1 {"Conditional Rendering"} ))}
|
|
||||||
|
|
||||||
// Child nodes
|
|
||||||
{cx.props.children}
|
|
||||||
|
|
||||||
// Any expression that is `IntoVNode`
|
|
||||||
{expr}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
|
@ -1,228 +0,0 @@
|
||||||
# RSX à Fundo
|
|
||||||
|
|
||||||
A macro RSX facilita muito a montagem de interfaces de usuário complexas com uma sintaxe Rust muito natural:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
rsx!(
|
|
||||||
div {
|
|
||||||
button {
|
|
||||||
onclick: move |e| todos.write().new_todo(),
|
|
||||||
"Add todo"
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
class: "todo-list",
|
|
||||||
todos.iter().map(|(key, todo)| rsx!(
|
|
||||||
li {
|
|
||||||
class: "beautiful-todo"
|
|
||||||
key: "f"
|
|
||||||
h3 { "{todo.title}" }
|
|
||||||
p { "{todo.contents}"}
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Nesta seção, abordaremos a macro `rsx!` em profundidade. Se você preferir aprender através de exemplos, o guia `referência de código` tem muitos exemplos sobre como usar `rsx!` efetivamente.
|
|
||||||
|
|
||||||
### Estrutura do elemento
|
|
||||||
|
|
||||||
Os atributos devem vir antes dos elementos filhos
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
div {
|
|
||||||
hidden: "false",
|
|
||||||
"some text"
|
|
||||||
child {}
|
|
||||||
Component {} // uppercase
|
|
||||||
component() // lowercase is treated like a function call
|
|
||||||
(0..10).map(|f| rsx!{ "hi {f}" }) // arbitrary rust expressions
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Cada elemento usa uma lista de expressões separadas por vírgulas para construir o nó. A grosso modo, veja como eles funcionam:
|
|
||||||
|
|
||||||
- `name: value` define uma propriedade neste elemento.
|
|
||||||
- `text` adiciona um novo elemento de texto
|
|
||||||
- `tag {}` adiciona um novo elemento filho
|
|
||||||
- `CustomTag {}` adiciona um novo componente filho
|
|
||||||
- `{expr}` cola os tokens `expr` literalmente. Eles devem ser `IntoIterator<T> where T: IntoVnode` para funcionar corretamente
|
|
||||||
|
|
||||||
As vírgulas são totalmente opcionais, mas podem ser úteis para delinear entre elementos e atributos.
|
|
||||||
|
|
||||||
A função `render` fornece um alocador **extremamente eficiente** para `VNodes` e `text`, então tente não usar a macro `format!` em seus componentes. Os métodos `ToString` padrão do Rust passam pelo alocador global, mas todo o texto nos componentes é alocado dentro de uma ""arena Bump"" gerenciada manualmente. Para levá-lo na direção certa, todos os atributos baseados em texto recebem `std::fmt::Arguments` diretamente, então você vai querer usar `format_args!` quando a interpolação interna `f-string` simplesmente não funcionar.
|
|
||||||
|
|
||||||
### Ignorando `cx.render` com `render!(...)`
|
|
||||||
|
|
||||||
Às vezes, escrever `cx.render` é um aborrecimento. O `rsx!` macro aceitará qualquer token seguido por uma vírgula como destino para chamar "render" em:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
cx.render(rsx!( div {} ))
|
|
||||||
// becomes
|
|
||||||
render!(div {})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Renderização Condicional
|
|
||||||
|
|
||||||
Às vezes, você pode não querer renderizar um elemento dada uma condição. O `rsx!` macro aceitará quaisquer tokens contidos diretamente com chaves, desde que resolvam para um tipo que implemente `IntoIterator<VNode>`. Isso nos permite escrever qualquer expressão Rust que resolva para um `VNode`:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
rsx!({
|
|
||||||
if enabled {
|
|
||||||
render!(div {"enabled"})
|
|
||||||
} else {
|
|
||||||
render!(li {"disabled"})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Uma maneira conveniente de ocultar/mostrar um elemento é retornar um `Option<VNode>`. Quando combinado com `and_then`, podemos controlar sucintamente o estado de exibição dado alguns booleanos:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
rsx!({
|
|
||||||
a.and_then(rsx!(div {"enabled"}))
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
É importante notar que a expressão `rsx!()` é tipicamente tardia - esta expressão deve ser _renderizada_ para produzir um `VNode`. Ao usar declarações de `match`, devemos renderizar todos os braços para evitar a regra 'não há dois fechamentos idênticos' que o Rust impõe:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
// this will not compile!
|
|
||||||
match case {
|
|
||||||
true => rsx!(div {}),
|
|
||||||
false => rsx!(div {})
|
|
||||||
}
|
|
||||||
|
|
||||||
// the nodes must be rendered first
|
|
||||||
match case {
|
|
||||||
true => render!(div {}),
|
|
||||||
false => render!(div {})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Listas
|
|
||||||
|
|
||||||
Novamente, porque qualquer coisa que implemente `IntoIterator<VNode>` é válida, podemos usar listas diretamente em nosso `rsx!`:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
let items = vec!["a", "b", "c"];
|
|
||||||
|
|
||||||
cx.render(rsx!{
|
|
||||||
ul {
|
|
||||||
{items.iter().map(|f| rsx!(li { "a" }))}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Às vezes, faz sentido renderizar `VNodes` em uma lista:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
let mut items = vec![];
|
|
||||||
|
|
||||||
for _ in 0..5 {
|
|
||||||
items.push(render!(li {} ))
|
|
||||||
}
|
|
||||||
|
|
||||||
render!({items} )
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Listas e chaves
|
|
||||||
|
|
||||||
Ao renderizar o `VirtualDom` na tela, o Dioxus precisa saber quais elementos foram adicionados e quais foram removidos. Essas mudanças são determinadas através de um processo chamado "diffing" - um antigo conjunto de elementos é comparado a um novo conjunto de elementos. Se um elemento for removido, ele não aparecerá nos novos elementos, e Dioxus sabe removê-lo.
|
|
||||||
|
|
||||||
No entanto, com listas, Dioxus não sabe exatamente como determinar quais elementos foram adicionados ou removidos se a ordem mudar ou se um elemento for adicionado ou removido do meio da lista.
|
|
||||||
|
|
||||||
Nesses casos, é de vital importância especificar uma "chave" ao lado do elemento. As chaves devem ser persistentes entre as renderizações.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
fn render_list(cx: Scope, items: HashMap<String, Todo>) -> DomTree {
|
|
||||||
render!(ul {
|
|
||||||
{items.iter().map(|key, item| {
|
|
||||||
li {
|
|
||||||
key: key,
|
|
||||||
h2 { "{todo.title}" }
|
|
||||||
p { "{todo.contents}" }
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Existem muitos guias feitos para chaves no React, então recomendamos a leitura para entender sua importância:
|
|
||||||
|
|
||||||
- [React guide on keys](https://reactjs.org/docs/lists-and-keys.html)
|
|
||||||
- [Importância das chaves (Média)](https://kentcdodds.com/blog/understanding-reacts-key-prop)
|
|
||||||
|
|
||||||
### Referência Completa
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
let text = "example";
|
|
||||||
|
|
||||||
cx.render(rsx!{
|
|
||||||
div {
|
|
||||||
h1 { "Example" },
|
|
||||||
|
|
||||||
{title}
|
|
||||||
|
|
||||||
// fstring interpolation
|
|
||||||
"{text}"
|
|
||||||
|
|
||||||
p {
|
|
||||||
// Attributes
|
|
||||||
tag: "type",
|
|
||||||
|
|
||||||
// Anything that implements display can be an attribute
|
|
||||||
abc: 123,
|
|
||||||
|
|
||||||
enabled: true,
|
|
||||||
|
|
||||||
// attributes also supports interpolation
|
|
||||||
// `class` is not a restricted keyword unlike JS and ClassName
|
|
||||||
class: "big small wide short {text}",
|
|
||||||
|
|
||||||
class: format_args!("attributes take fmt::Arguments. {}", 99),
|
|
||||||
|
|
||||||
tag: {"these tokens are placed directly"}
|
|
||||||
|
|
||||||
// Children
|
|
||||||
a { "abcder" },
|
|
||||||
|
|
||||||
// Children with attributes
|
|
||||||
h2 { "hello", class: "abc-123" },
|
|
||||||
|
|
||||||
// Child components
|
|
||||||
CustomComponent { a: 123, b: 456, key: "1" },
|
|
||||||
|
|
||||||
// Child components with paths
|
|
||||||
crate::components::CustomComponent { a: 123, b: 456, key: "1" },
|
|
||||||
|
|
||||||
// Iterators
|
|
||||||
{ (0..3).map(|i| rsx!( h1 {"{:i}"} )) },
|
|
||||||
|
|
||||||
// More rsx!, or even html!
|
|
||||||
{ rsx! { div { } } },
|
|
||||||
{ html! { <div> </div> } },
|
|
||||||
|
|
||||||
// Matching
|
|
||||||
// Requires rendering the nodes first.
|
|
||||||
// rsx! is lazy, and the underlying closures cannot have the same type
|
|
||||||
// Rendering produces the VNode type
|
|
||||||
{match rand::gen_range::<i32>(1..3) {
|
|
||||||
1 => render!(h1 { "big" })
|
|
||||||
2 => render!(h2 { "medium" })
|
|
||||||
_ => render!(h3 { "small" })
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Optionals
|
|
||||||
{true.and_then(|f| rsx!( h1 {"Conditional Rendering"} ))}
|
|
||||||
|
|
||||||
// Child nodes
|
|
||||||
{cx.props.children}
|
|
||||||
|
|
||||||
// Any expression that is `IntoVNode`
|
|
||||||
{expr}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
|
@ -1,45 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
||||||
Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over the menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. The next major release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc.
|
|
||||||
|
|
||||||
## Getting Set up
|
|
||||||
|
|
||||||
Getting Set up with Dioxus-Desktop is quite easy. Make sure you have Rust and Cargo installed, and then create a new project:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ cargo new --bin demo
|
|
||||||
$ cd demo
|
|
||||||
```
|
|
||||||
|
|
||||||
Add Dioxus with the `desktop` feature:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ cargo add dioxus --features desktop
|
|
||||||
```
|
|
||||||
|
|
||||||
Edit your `main.rs`:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
// main.rs
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
dioxus::desktop::launch(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx!{
|
|
||||||
div {
|
|
||||||
"hello world!"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To configure the webview, menubar, and other important desktop-specific features, checkout out some of the launch configuration in the [API reference](https://docs.rs/dioxus-desktop/).
|
|
||||||
|
|
||||||
## Future Steps
|
|
||||||
|
|
||||||
Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide/en) if you already haven't!
|
|
|
@ -1,11 +0,0 @@
|
||||||
# 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)
|
|
14
docs/router/Cargo.toml
Normal file
14
docs/router/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "dioxus-router-guide"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Dioxus router guide, including testable examples"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
dioxus = { path = "../../packages/dioxus" }
|
||||||
|
dioxus-desktop = { path = "../../packages/desktop" }
|
||||||
|
dioxus-web = { path = "../../packages/web" }
|
||||||
|
dioxus-ssr = { path = "../../packages/ssr" }
|
||||||
|
dioxus-router = { path = "../../packages/router" }
|
7
docs/router/README.md
Normal file
7
docs/router/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# The router book
|
||||||
|
|
||||||
|
## How to run the tests
|
||||||
|
- Navigate your terminal to this directory
|
||||||
|
- Run `cargo clean`
|
||||||
|
- Run `cargo build --all --F regex -F serde -F web`
|
||||||
|
- Run `mdbook test -L ../../target/debug/deps/`
|
|
@ -4,3 +4,6 @@ language = "en"
|
||||||
multilingual = false
|
multilingual = false
|
||||||
src = "src"
|
src = "src"
|
||||||
title = "Dioxus Router"
|
title = "Dioxus Router"
|
||||||
|
|
||||||
|
[rust]
|
||||||
|
edition = "2021"
|
||||||
|
|
48
docs/router/examples/catch_all.rs
Normal file
48
docs/router/examples/catch_all.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: router
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
enum Route {
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
// PageNotFound is a catch all route that will match any route and placing the matched segments in the route field
|
||||||
|
#[route("/:..route")]
|
||||||
|
PageNotFound { route: Vec<String> },
|
||||||
|
}
|
||||||
|
// ANCHOR_END: router
|
||||||
|
|
||||||
|
// ANCHOR: app
|
||||||
|
#[inline_props]
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: app
|
||||||
|
|
||||||
|
// ANCHOR: home
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: home
|
||||||
|
|
||||||
|
// ANCHOR: fallback
|
||||||
|
#[inline_props]
|
||||||
|
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Page not found" }
|
||||||
|
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
||||||
|
pre {
|
||||||
|
color: "red",
|
||||||
|
"log:\nattemped to navigate to: {route:?}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: fallback
|
||||||
|
|
||||||
|
fn main() {}
|
24
docs/router/examples/catch_all_segments.rs
Normal file
24
docs/router/examples/catch_all_segments.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: route
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// segments that start with :... are catch all segments
|
||||||
|
#[route("/blog/:..segments")]
|
||||||
|
BlogPost {
|
||||||
|
// You must include catch all segment in child variants
|
||||||
|
segments: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Components must contain the same catch all segments as their corresponding variant
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogPost(cx: Scope, segments: Vec<String>) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
// ANCHOR_END: route
|
||||||
|
|
||||||
|
fn main() {}
|
115
docs/router/examples/dynamic_route.rs
Normal file
115
docs/router/examples/dynamic_route.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
dioxus_web::launch(App);
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
dioxus_desktop::launch(App);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCHOR: router
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[layout(NavBar)]
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
#[nest("/blog")]
|
||||||
|
#[layout(Blog)]
|
||||||
|
#[route("/")]
|
||||||
|
BlogList {},
|
||||||
|
#[route("/blog/:name")]
|
||||||
|
BlogPost { name: String },
|
||||||
|
#[end_layout]
|
||||||
|
#[end_nest]
|
||||||
|
#[end_layout]
|
||||||
|
#[route("/:..route")]
|
||||||
|
PageNotFound {
|
||||||
|
route: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// ANCHOR_END: router
|
||||||
|
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn NavBar(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
nav {
|
||||||
|
ul {
|
||||||
|
li { Link { target: Route::Home {}, "Home" } }
|
||||||
|
li { Link { target: Route::BlogList {}, "Blog" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Outlet {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCHOR: blog
|
||||||
|
#[inline_props]
|
||||||
|
fn Blog(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Blog" }
|
||||||
|
Outlet {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: blog
|
||||||
|
|
||||||
|
// ANCHOR: blog_list
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogList(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h2 { "Choose a post" }
|
||||||
|
ul {
|
||||||
|
li {
|
||||||
|
Link {
|
||||||
|
target: Route::BlogPost { name: "Blog post 1".into() },
|
||||||
|
"Read the first blog post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
Link {
|
||||||
|
target: Route::BlogPost { name: "Blog post 2".into() },
|
||||||
|
"Read the second blog post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: blog_list
|
||||||
|
|
||||||
|
// ANCHOR: blog_post
|
||||||
|
// The name prop comes from the /:name route segment
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogPost(cx: Scope, name: String) -> Element {
|
||||||
|
render! {
|
||||||
|
h2 { "Blog Post: {name}"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: blog_post
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Page not found" }
|
||||||
|
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
||||||
|
pre {
|
||||||
|
color: "red",
|
||||||
|
"log:\nattemped to navigate to: {route:?}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
docs/router/examples/dynamic_segments.rs
Normal file
35
docs/router/examples/dynamic_segments.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: route
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// segments that start with : are dynamic segments
|
||||||
|
#[route("/blog/:name")]
|
||||||
|
BlogPost {
|
||||||
|
// You must include dynamic segments in child variants
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
#[route("/document/:id")]
|
||||||
|
Document {
|
||||||
|
// You can use any type that implements FromStr
|
||||||
|
// If the segment can't be parsed, the route will not match
|
||||||
|
id: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Components must contain the same dynamic segments as their corresponding variant
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogPost(cx: Scope, name: String) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Document(cx: Scope, id: usize) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
// ANCHOR_END: route
|
||||||
|
|
||||||
|
fn main() {}
|
28
docs/router/examples/external_link.rs
Normal file
28
docs/router/examples/external_link.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
enum Route {
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
// ANCHOR: component
|
||||||
|
fn GoToDioxus(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Link {
|
||||||
|
target: NavigationTarget::External("https://dioxuslabs.com".into()),
|
||||||
|
"ExternalTarget target"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: component
|
36
docs/router/examples/first_route.rs
Normal file
36
docs/router/examples/first_route.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// ANCHOR: router
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
/// An enum of all of the possible routes in the app.
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
enum Route {
|
||||||
|
// The home page is at the / route
|
||||||
|
#[route("/")]
|
||||||
|
// If the name of the component and variant are the same you can omit the component and props name
|
||||||
|
// If they are different you can specify them like this:
|
||||||
|
// #[route("/", ComponentName, PropsName)]
|
||||||
|
Home {},
|
||||||
|
}
|
||||||
|
// ANCHOR_END: router
|
||||||
|
|
||||||
|
// ANCHOR: app
|
||||||
|
#[inline_props]
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: app
|
||||||
|
|
||||||
|
// ANCHOR: home
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: home
|
||||||
|
|
||||||
|
fn main() {}
|
112
docs/router/examples/full_example.rs
Normal file
112
docs/router/examples/full_example.rs
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
dioxus_web::launch(App);
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
dioxus_desktop::launch(App);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCHOR: router
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[layout(NavBar)]
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
#[nest("/blog")]
|
||||||
|
#[layout(Blog)]
|
||||||
|
#[route("/")]
|
||||||
|
BlogList {},
|
||||||
|
#[route("/blog/:name")]
|
||||||
|
BlogPost { name: String },
|
||||||
|
#[end_layout]
|
||||||
|
#[end_nest]
|
||||||
|
#[end_layout]
|
||||||
|
#[nest("/myblog")]
|
||||||
|
#[redirect("/", || Route::BlogList {})]
|
||||||
|
#[redirect("/:name", |name: String| Route::BlogPost { name })]
|
||||||
|
#[end_nest]
|
||||||
|
#[route("/:..route")]
|
||||||
|
PageNotFound {
|
||||||
|
route: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// ANCHOR_END: router
|
||||||
|
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn NavBar(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
nav {
|
||||||
|
ul {
|
||||||
|
li { Link { target: Route::Home {}, "Home" } }
|
||||||
|
li { Link { target: Route::BlogList {}, "Blog" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Outlet {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Blog(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Blog" }
|
||||||
|
Outlet {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogList(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h2 { "Choose a post" }
|
||||||
|
ul {
|
||||||
|
li {
|
||||||
|
Link {
|
||||||
|
target: Route::BlogPost { name: "Blog post 1".into() },
|
||||||
|
"Read the first blog post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
Link {
|
||||||
|
target: Route::BlogPost { name: "Blog post 2".into() },
|
||||||
|
"Read the second blog post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogPost(cx: Scope, name: String) -> Element {
|
||||||
|
render! {
|
||||||
|
h2 { "Blog Post: {name}"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Page not found" }
|
||||||
|
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
||||||
|
pre {
|
||||||
|
color: "red",
|
||||||
|
"log:\nattemped to navigate to: {route:?}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
docs/router/examples/history_buttons.rs
Normal file
30
docs/router/examples/history_buttons.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCHOR: history_buttons
|
||||||
|
fn HistoryNavigation(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
GoBackButton {
|
||||||
|
"Back to the Past"
|
||||||
|
}
|
||||||
|
GoForwardButton {
|
||||||
|
"Back to the Future" /* You see what I did there? 😉 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: history_buttons
|
||||||
|
|
||||||
|
fn main() {}
|
29
docs/router/examples/history_provider.rs
Normal file
29
docs/router/examples/history_provider.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
enum Route {
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCHOR: app
|
||||||
|
#[inline_props]
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {
|
||||||
|
config: || RouterConfig::default().history(WebHistory::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: app
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
72
docs/router/examples/links.rs
Normal file
72
docs/router/examples/links.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: router
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// All routes under the NavBar layout will be rendered inside of the NavBar Outlet
|
||||||
|
#[layout(NavBar)]
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
#[end_layout]
|
||||||
|
#[route("/:..route")]
|
||||||
|
PageNotFound { route: Vec<String> },
|
||||||
|
}
|
||||||
|
// ANCHOR_END: router
|
||||||
|
|
||||||
|
// ANCHOR: nav
|
||||||
|
#[inline_props]
|
||||||
|
fn NavBar(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
nav {
|
||||||
|
ul {
|
||||||
|
li {
|
||||||
|
Link {
|
||||||
|
// The Link component will navigate to the route specified
|
||||||
|
// in the target prop which is checked to exist at compile time
|
||||||
|
target: Route::Home {},
|
||||||
|
"Home"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Outlet {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: nav
|
||||||
|
|
||||||
|
// ANCHOR: app
|
||||||
|
#[inline_props]
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: app
|
||||||
|
|
||||||
|
// ANCHOR: home
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: home
|
||||||
|
|
||||||
|
// ANCHOR: fallback
|
||||||
|
#[inline_props]
|
||||||
|
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Page not found" }
|
||||||
|
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
||||||
|
pre {
|
||||||
|
color: "red",
|
||||||
|
"log:\nattemped to navigate to: {route:?}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: fallback
|
||||||
|
|
||||||
|
fn main() {}
|
56
docs/router/examples/navigator.rs
Normal file
56
docs/router/examples/navigator.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
#[route("/:..route")]
|
||||||
|
PageNotFound { route: Vec<String> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCHOR: nav
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
let nav = use_navigator(cx);
|
||||||
|
|
||||||
|
// push
|
||||||
|
nav.push(Route::PageNotFound { route: vec![] });
|
||||||
|
|
||||||
|
// replace
|
||||||
|
nav.replace(Route::Home {});
|
||||||
|
|
||||||
|
// go back
|
||||||
|
nav.go_back();
|
||||||
|
|
||||||
|
// go forward
|
||||||
|
nav.go_forward();
|
||||||
|
|
||||||
|
render! {
|
||||||
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: nav
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Page not found" }
|
||||||
|
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
||||||
|
pre {
|
||||||
|
color: "red",
|
||||||
|
"log:\nattemped to navigate to: {route:?}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
40
docs/router/examples/nest.rs
Normal file
40
docs/router/examples/nest.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: route
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
// Skipping formatting allows you to indent nests
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// Start the /blog nest
|
||||||
|
#[nest("/blog")]
|
||||||
|
// You can nest as many times as you want
|
||||||
|
#[nest("/:id")]
|
||||||
|
#[route("/post")]
|
||||||
|
PostId {
|
||||||
|
// You must include parent dynamic segments in child variants
|
||||||
|
id: usize,
|
||||||
|
},
|
||||||
|
// End nests manually with #[end_nest]
|
||||||
|
#[end_nest]
|
||||||
|
#[route("/:id")]
|
||||||
|
// The absolute route of BlogPost is /blog/:name
|
||||||
|
BlogPost {
|
||||||
|
id: usize,
|
||||||
|
},
|
||||||
|
// Or nests are ended automatically at the end of the enum
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogPost(cx: Scope, id: usize) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn PostId(cx: Scope, id: usize) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
// ANCHOR_END: route
|
||||||
|
|
||||||
|
fn main() {}
|
66
docs/router/examples/nested_routes.rs
Normal file
66
docs/router/examples/nested_routes.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: router
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// All routes under the NavBar layout will be rendered inside of the NavBar Outlet
|
||||||
|
#[layout(NavBar)]
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
#[end_layout]
|
||||||
|
#[route("/:..route")]
|
||||||
|
PageNotFound { route: Vec<String> },
|
||||||
|
}
|
||||||
|
// ANCHOR_END: router
|
||||||
|
|
||||||
|
// ANCHOR: nav
|
||||||
|
#[inline_props]
|
||||||
|
fn NavBar(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
nav {
|
||||||
|
ul {
|
||||||
|
li { "links" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The Outlet component will render child routes (In this case just the Home component) inside the Outlet component
|
||||||
|
Outlet {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: nav
|
||||||
|
|
||||||
|
// ANCHOR: app
|
||||||
|
#[inline_props]
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: app
|
||||||
|
|
||||||
|
// ANCHOR: home
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: home
|
||||||
|
|
||||||
|
// ANCHOR: fallback
|
||||||
|
#[inline_props]
|
||||||
|
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Page not found" }
|
||||||
|
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
||||||
|
pre {
|
||||||
|
color: "red",
|
||||||
|
"log:\nattemped to navigate to: {route:?}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: fallback
|
||||||
|
|
||||||
|
fn main() {}
|
46
docs/router/examples/outlet.rs
Normal file
46
docs/router/examples/outlet.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: outlet
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[layout(Wrapper)]
|
||||||
|
#[route("/")]
|
||||||
|
Index {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Wrapper(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
header { "header" }
|
||||||
|
// The index route will be rendered here
|
||||||
|
Outlet { }
|
||||||
|
footer { "footer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Index(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Index" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: outlet
|
||||||
|
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut vdom = VirtualDom::new(App);
|
||||||
|
let _ = vdom.rebuild();
|
||||||
|
let html = dioxus_ssr::render(&vdom);
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"<header>header</header><h1>Index</h1><footer>footer</footer>"
|
||||||
|
);
|
||||||
|
}
|
24
docs/router/examples/query_segments.rs
Normal file
24
docs/router/examples/query_segments.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: route
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// segments that start with ?: are query segments
|
||||||
|
#[route("/blog?:name")]
|
||||||
|
BlogPost {
|
||||||
|
// You must include query segments in child variants
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Components must contain the same query segments as their corresponding variant
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogPost(cx: Scope, name: String) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
// ANCHOR_END: route
|
||||||
|
|
||||||
|
fn main() {}
|
37
docs/router/examples/router_cfg.rs
Normal file
37
docs/router/examples/router_cfg.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// ANCHOR: router
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
/// An enum of all of the possible routes in the app.
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
enum Route {
|
||||||
|
// The home page is at the / route
|
||||||
|
#[route("/")]
|
||||||
|
// If the name of the component and variant are the same you can omit the component and props name
|
||||||
|
// #[route("/", ComponentName, PropsName)]
|
||||||
|
Home {},
|
||||||
|
}
|
||||||
|
// ANCHOR_END: router
|
||||||
|
|
||||||
|
// ANCHOR: app
|
||||||
|
#[inline_props]
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {
|
||||||
|
config: || RouterConfig::default().history(WebHistory::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: app
|
||||||
|
|
||||||
|
// ANCHOR: home
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: home
|
||||||
|
|
||||||
|
fn main() {}
|
41
docs/router/examples/routing_update.rs
Normal file
41
docs/router/examples/routing_update.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: router
|
||||||
|
#[derive(Routable, Clone, PartialEq)]
|
||||||
|
enum Route {
|
||||||
|
#[route("/")]
|
||||||
|
Index {},
|
||||||
|
#[route("/home")]
|
||||||
|
Home {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
p { "Home" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Index(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
p { "Index" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {
|
||||||
|
config: || RouterConfig::default().on_update(|state|{
|
||||||
|
(state.current() == Route::Index {}).then_some(
|
||||||
|
NavigationTarget::Internal(Route::Home {})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: router
|
||||||
|
|
||||||
|
fn main() {}
|
28
docs/router/examples/static_segments.rs
Normal file
28
docs/router/examples/static_segments.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: route
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// Routes always start with a slash
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
// You can have multiple segments in a route
|
||||||
|
#[route("/hello/world")]
|
||||||
|
HelloWorld {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn HelloWorld(cx: Scope) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
// ANCHOR_END: route
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -1,11 +0,0 @@
|
||||||
# Dioxus Router: Introduction
|
|
||||||
|
|
||||||
Whether or not you're building a website, desktop app, or mobile app, organizing your app's views into "pages" can be an effective method for organization and maintainability.
|
|
||||||
|
|
||||||
The `dioxus-router` crate contains the Router module. To add it to your project run:
|
|
||||||
|
|
||||||
cargo add dioxus-router
|
|
||||||
|
|
||||||
> **Be sure to include the `web` feature (`--feature web`) for deployment into a browser!**
|
|
||||||
|
|
||||||
In this book you'll find a short [guide](./guide/index.md) to get up to speed with Dioxus Router, as well as the router's [reference](./reference/index.md).
|
|
|
@ -1,11 +1,25 @@
|
||||||
# Summary
|
# Summary
|
||||||
|
|
||||||
- [Introduction](./README.md)
|
[Introduction](./index.md)
|
||||||
|
|
||||||
- [Guide](./guide/index.md)
|
# Example Project
|
||||||
- [Getting Started](./guide/getting-started.md)
|
|
||||||
- [Creating Our First Route](./guide/first-route.md)
|
- [Overview](./example/index.md)
|
||||||
- [Building a Nest](./guide/building-a-nest.md)
|
- [Creating Our First Route](./example/first-route.md)
|
||||||
- [Redirection Perfection](./guide/redirection-perfection.md)
|
- [Building a Nest](./example/building-a-nest.md)
|
||||||
- [Reference](./reference/index.md)
|
- [Navigation Targets](./example/navigation-targets.md)
|
||||||
- [X]()
|
- [Redirection Perfection](./example/redirection-perfection.md)
|
||||||
|
- [Full Code](./example/full-code.md)
|
||||||
|
|
||||||
|
# Reference
|
||||||
|
|
||||||
|
- [Adding the Router to Your Application](./reference/index.md)
|
||||||
|
- [Defining Routes](./reference/routes/index.md)
|
||||||
|
- [Nested Routes](./reference/routes/nested.md)
|
||||||
|
- [Layouts](./reference/layouts.md)
|
||||||
|
- [Navigation](./reference/navigation/index.md)
|
||||||
|
- [Programmatic Navigation](./reference/navigation/programmatic.md)
|
||||||
|
- [History Providers](./reference/history-providers.md)
|
||||||
|
- [History Buttons](./reference/history-buttons.md)
|
||||||
|
- [Static Generation](./reference/static-generation.md)
|
||||||
|
- [Routing Update Callback](./reference/routing-update-callback.md)
|
||||||
|
|
99
docs/router/src/example/building-a-nest.md
Normal file
99
docs/router/src/example/building-a-nest.md
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
# Building a Nest
|
||||||
|
|
||||||
|
In this chapter, we will begin to build the blog portion of our site which will
|
||||||
|
include links, nested routes, and route parameters.
|
||||||
|
|
||||||
|
## Site Navigation
|
||||||
|
|
||||||
|
Our site visitors won't know all the available pages and blogs on our site so we
|
||||||
|
should provide a navigation bar for them. Our navbar will be a list of links going between our pages.
|
||||||
|
|
||||||
|
We want our navbar component to be rendered on several different pages on our site. Instead of duplicating the code, we can create a component that wraps all children routes. This is called a layout component. To tell the router where to render the child routes, we use the [`Outlet`] component.
|
||||||
|
|
||||||
|
Let's create a new `NavBar` component:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/nested_routes.rs:nav}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, let's add our `NavBar` component as a layout to our Route enum:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/nested_routes.rs:router}}
|
||||||
|
```
|
||||||
|
|
||||||
|
To add links to our `NavBar`, we could always use an HTML anchor element but that has two issues:
|
||||||
|
|
||||||
|
1. It causes a full-page reload
|
||||||
|
2. We can accidentally link to a page that doesn't exist
|
||||||
|
|
||||||
|
Instead, we want to use the [`Link`] component provided by Dioxus Router.
|
||||||
|
|
||||||
|
The [`Link`] is similar to a regular `<a>` tag. It takes a target and children.
|
||||||
|
|
||||||
|
Unlike a regular `<a>` tag, we can pass in our Route enum as the target. Because we annotated our routes with the [`route(path)`] attribute, the [`Link`] will know how to generate the correct URL. If we use the Route enum, the rust compiler will prevent us from linking to a page that doesn't exist.
|
||||||
|
|
||||||
|
Let's add our links:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/links.rs:nav}}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Using this method, the [`Link`] component only works for links within our
|
||||||
|
> application. To learn more about navigation targets see
|
||||||
|
> [here](./navigation-targets.md).
|
||||||
|
|
||||||
|
Now you should see a list of links near the top of your page. Click on one and
|
||||||
|
you should seamlessly travel between pages.
|
||||||
|
|
||||||
|
## URL Parameters and Nested Routes
|
||||||
|
|
||||||
|
Many websites such as GitHub put parameters in their URL. For example,
|
||||||
|
`https://github.com/DioxusLabs` utilizes the text after the domain to
|
||||||
|
dynamically search and display content about an organization.
|
||||||
|
|
||||||
|
We want to store our blogs in a database and load them as needed. We also
|
||||||
|
want our users to be able to send people a link to a specific blog post.
|
||||||
|
Instead of listing all of the blog titles at compile time, we can make a dynamic route.
|
||||||
|
|
||||||
|
We could utilize a search page that loads a blog when clicked but then our users
|
||||||
|
won't be able to share our blogs easily. This is where URL parameters come in.
|
||||||
|
|
||||||
|
The path to our blog will look like `/blog/myBlogPage`, `myBlogPage` being the
|
||||||
|
URL parameter.
|
||||||
|
|
||||||
|
First, let's create a layout component (similar to the navbar) that wraps the blog content. This allows us to add a heading that tells the user they are on the blog.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/dynamic_route.rs:blog}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we'll create another index component, that'll be displayed when no blog post
|
||||||
|
is selected:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/dynamic_route.rs:blog_list}}
|
||||||
|
```
|
||||||
|
|
||||||
|
We also need to create a component that displays an actual blog post. This component will accept the URL parameters as props:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/dynamic_route.rs:blog_post}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, let's tell our router about those components:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/dynamic_route.rs:router}}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! If you head to `/blog/1` you should see our sample post.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
In this chapter, we utilized Dioxus Router's Link, and Route Parameter
|
||||||
|
functionality to build the blog portion of our application. In the next chapter,
|
||||||
|
we will go over how navigation targets (like the one we passed to our links)
|
||||||
|
work.
|
||||||
|
|
||||||
|
[`Link`]: https://docs.rs/dioxus-router/latest/dioxus_router/prelude/fn.GenericLink<R>.html
|
62
docs/router/src/example/first-route.md
Normal file
62
docs/router/src/example/first-route.md
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# Creating Our First Route
|
||||||
|
|
||||||
|
In this chapter, we will start utilizing Dioxus Router and add a homepage and a
|
||||||
|
404 page to our project.
|
||||||
|
|
||||||
|
## Fundamentals
|
||||||
|
|
||||||
|
The core of the Dioxus Router is the [`Routable`] macro and the [`Router`] component.
|
||||||
|
|
||||||
|
First, we need an actual page to route to! Let's add a homepage component:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/first_route.rs:home}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Creating Routes
|
||||||
|
|
||||||
|
We want to use Dioxus Router to separate our application into different "pages".
|
||||||
|
Dioxus Router will then determine which page to render based on the URL path.
|
||||||
|
|
||||||
|
To start using Dioxus Router, we need to use the [`Routable`] macro.
|
||||||
|
|
||||||
|
The [`Routable`] macro takes an enum with all of the possible routes in our application. Each variant of the enum represents a route and must be annotated with the [`route(path)`] attribute.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/first_route.rs:router}}
|
||||||
|
```
|
||||||
|
|
||||||
|
All other hooks and components the router provides can only be used as a descendant of a [`Router`] component.
|
||||||
|
|
||||||
|
If you head to your application's browser tab, you should now see the text
|
||||||
|
`Welcome to Dioxus Blog!` when on the root URL (`http://localhost:8080/`). If
|
||||||
|
you enter a different path for the URL, nothing should be displayed.
|
||||||
|
|
||||||
|
This is because we told Dioxus Router to render the `Home` component only when
|
||||||
|
the URL path is `/`.
|
||||||
|
|
||||||
|
## Fallback Route
|
||||||
|
|
||||||
|
In our example, when a route doesn't exist Dioxus Router doesn't render anything. Many sites also have a "404" page when a path does not exist. Let's add one to our site.
|
||||||
|
|
||||||
|
First, we create a new `PageNotFound` component.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/catch_all.rs:fallback}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, register the route in the Route enum to match if all other routes fail.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/catch_all.rs:router}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now when you go to a route that doesn't exist, you should see the page not found
|
||||||
|
text.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
In this chapter, we learned how to create a route and tell Dioxus Router what
|
||||||
|
component to render when the URL path is `/`. We also created a 404 page to
|
||||||
|
handle when a route doesn't exist. Next, we'll create the blog portion of our
|
||||||
|
site. We will utilize nested routes and URL parameters.
|
5
docs/router/src/example/full-code.md
Normal file
5
docs/router/src/example/full-code.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Full Code
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/full_example.rs}}
|
||||||
|
```
|
29
docs/router/src/example/index.md
Normal file
29
docs/router/src/example/index.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
In this guide, you'll learn to effectively use Dioxus Router whether you're
|
||||||
|
building a small todo app or the next FAANG company. We will create a small
|
||||||
|
website with a blog, homepage, and more!
|
||||||
|
|
||||||
|
> To follow along with the router example, you'll need a working Dioxus app.
|
||||||
|
> Check out the [Dioxus book](https://dioxuslabs.com/docs/0.3/guide/en/) to get started.
|
||||||
|
|
||||||
|
> Make sure to add Dioxus Router as a dependency, as explained in the
|
||||||
|
> [introduction](../index.md).
|
||||||
|
|
||||||
|
## You'll learn how to
|
||||||
|
|
||||||
|
- Create routes and render "pages".
|
||||||
|
- Utilize nested routes, create a navigation bar, and render content for a
|
||||||
|
set of routes.
|
||||||
|
- Parse URL parameters to dynamically display content.
|
||||||
|
- Redirect visitors to different routes.
|
||||||
|
|
||||||
|
> **Disclaimer**
|
||||||
|
>
|
||||||
|
> The example will only display the features of Dioxus Router. It will not
|
||||||
|
> include any actual functionality. To keep things simple we will only be using
|
||||||
|
> a single file, this is not the recommended way of doing things with a real
|
||||||
|
> application.
|
||||||
|
|
||||||
|
You can find the complete application in the [full code](./full-code.md)
|
||||||
|
chapter.
|
27
docs/router/src/example/navigation-targets.md
Normal file
27
docs/router/src/example/navigation-targets.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Navigation Targets
|
||||||
|
|
||||||
|
In the previous chapter, we learned how to create links to pages within our app.
|
||||||
|
We told them where to go using the `target` property. This property takes something that can be converted to a [`NavigationTarget`].
|
||||||
|
|
||||||
|
## What is a navigation target?
|
||||||
|
|
||||||
|
A [`NavigationTarget`] is similar to the `href` of an HTML anchor element. It
|
||||||
|
tells the router where to navigate to. The Dioxus Router knows two kinds of
|
||||||
|
navigation targets:
|
||||||
|
|
||||||
|
- [`Internal`]: We used internal links in the previous chapter. It's a link to a page within our
|
||||||
|
app represented as a Route enum.
|
||||||
|
- [`External`]: This works exactly like an HTML anchors' `href`. Don't use this for in-app
|
||||||
|
navigation as it will trigger a page reload by the browser.
|
||||||
|
|
||||||
|
## External navigation
|
||||||
|
|
||||||
|
If we need a link to an external page we can do it like this:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/external_link.rs:component}}
|
||||||
|
```
|
||||||
|
|
||||||
|
[`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.External
|
||||||
|
[`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.Internal
|
||||||
|
[`NavigationTarget`]: https://docs.rs/dioxus-router-core/latest/dioxus_router/navigation/enum.NavigationTarget.html
|
41
docs/router/src/example/redirection-perfection.md
Normal file
41
docs/router/src/example/redirection-perfection.md
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# Redirection Perfection
|
||||||
|
|
||||||
|
You're well on your way to becoming a routing master!
|
||||||
|
|
||||||
|
In this chapter, we will cover creating redirects
|
||||||
|
|
||||||
|
## Creating Redirects
|
||||||
|
|
||||||
|
A redirect is very simple. When dioxus encounters a redirect while finding out
|
||||||
|
what components to render, it will redirect the user to the target of the
|
||||||
|
redirect.
|
||||||
|
|
||||||
|
As a simple example, let's say you want user to still land on your blog, even
|
||||||
|
if they used the path `/myblog` or `/myblog/:name`.
|
||||||
|
|
||||||
|
Redirects are special attributes in the router enum that accept a route and a closure
|
||||||
|
with the route parameters. The closure should return a route to redirect to.
|
||||||
|
|
||||||
|
Let's add a redirect to our router enum:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/full_example.rs:router}}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! Now your users will be redirected to the blog.
|
||||||
|
|
||||||
|
### Conclusion
|
||||||
|
|
||||||
|
Well done! You've completed the Dioxus Router guide. You've built a small
|
||||||
|
application and learned about the many things you can do with Dioxus Router.
|
||||||
|
To continue your journey, you attempt a challenge listed below, look at the [router examples](https://github.com/DioxusLabs/dioxus/tree/master/packages/router/examples), or
|
||||||
|
can check out the [API reference](https://docs.rs/dioxus-router/).
|
||||||
|
|
||||||
|
### Challenges
|
||||||
|
|
||||||
|
- Organize your components into separate files for better maintainability.
|
||||||
|
- Give your app some style if you haven't already.
|
||||||
|
- Build an about page so your visitors know who you are.
|
||||||
|
- Add a user system that uses URL parameters.
|
||||||
|
- Create a simple admin system to create, delete, and edit blogs.
|
||||||
|
- If you want to go to the max, hook up your application to a rest API and database.
|
|
@ -1,201 +0,0 @@
|
||||||
# Building a Nest
|
|
||||||
Not a bird's nest! A nest of routes!
|
|
||||||
|
|
||||||
In this chapter we will begin to build the blog portion of our site which will include links, nested URLs, and URL parameters. We will also explore the use case of rendering components outside of routes.
|
|
||||||
|
|
||||||
### Site Navigation
|
|
||||||
Our site visitors won't know all the available pages and blogs on our site so we should provide a navigation bar for them.
|
|
||||||
Let's create a new ``navbar`` component:
|
|
||||||
```rs
|
|
||||||
fn navbar(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
ul {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Our navbar will be a list of links going between our pages. We could always use an HTML anchor element but that would cause our page to unnecessarily reload. Instead we want to use the ``Link`` component provided by Dioxus Router.
|
|
||||||
|
|
||||||
The Link component is very similar to the Route component. It takes a path and an element. Add the Link component into your use statement and then add some links:
|
|
||||||
```rs
|
|
||||||
use dioxus::{
|
|
||||||
prelude::*,
|
|
||||||
router::{Route, Router, Link}, // UPDATED
|
|
||||||
};
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
fn navbar(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
ul {
|
|
||||||
// NEW
|
|
||||||
Link { to: "/", "Home"}
|
|
||||||
br {}
|
|
||||||
Link { to: "/blog", "Blog"}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
>By default, the Link component only works for links within your application. To link to external sites, add the ``external: true`` property.
|
|
||||||
>```rs
|
|
||||||
>Link { to: "https://github.com", external: true, "GitHub"}
|
|
||||||
>```
|
|
||||||
|
|
||||||
And finally, use the navbar component in your app component:
|
|
||||||
```rs
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
Router {
|
|
||||||
p { "-- Dioxus Blog --" }
|
|
||||||
self::navbar {} // NEW
|
|
||||||
Route { to: "/", self::homepage {}}
|
|
||||||
Route { to: "", self::page_not_found {}}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Now you should see a list of links near the top of your page. Click on one and you should seamlessly travel between pages.
|
|
||||||
|
|
||||||
##### WIP: Active Link Styling
|
|
||||||
|
|
||||||
### URL Parameters and Nested Routes
|
|
||||||
Many websites such as GitHub put parameters in their URL. For example, ``github.com/DioxusLabs`` utilizes the text after the domain to dynamically search and display content about an organization.
|
|
||||||
|
|
||||||
We want to store our blogs in a database and load them as needed. This'll help prevent our app from being bloated therefor providing faster load times. We also want our users to be able to send people a link to a specific blog post.
|
|
||||||
We could utilize a search page that loads a blog when clicked but then our users won't be able to share our blogs easily. This is where URL parameters come in. And finally, we also want our site to tell users they are on a blog page whenever the URL starts with``/blog``.
|
|
||||||
|
|
||||||
The path to our blog will look like ``/blog/myBlogPage``. ``myBlogPage`` being the URL parameter.
|
|
||||||
Dioxus Router uses the ``:name`` pattern so our route will look like ``/blog/:post``.
|
|
||||||
|
|
||||||
First, lets tell users when they are on a blog page. Add a new route in your app component.
|
|
||||||
```rs
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
Router {
|
|
||||||
p { "-- Dioxus Blog --" }
|
|
||||||
self::navbar {}
|
|
||||||
Route { to: "/", self::homepage {}}
|
|
||||||
// NEW
|
|
||||||
Route {
|
|
||||||
to: "/blog",
|
|
||||||
}
|
|
||||||
Route { to: "", self::page_not_found {}}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Routes can take components as parameters and we know that a route is a component. We nest routes by doing exactly what they are called, nesting them:
|
|
||||||
```rs
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
Router {
|
|
||||||
p { "-- Dioxus Blog --" }
|
|
||||||
self::navbar {}
|
|
||||||
Route { to: "/", self::homepage {}}
|
|
||||||
Route {
|
|
||||||
to: "/blog",
|
|
||||||
Route { to: "/:post", "This is my blog post!" } // NEW
|
|
||||||
}
|
|
||||||
Route { to: "", self::page_not_found {}}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Nesting our route like this isn't too helpful at first, but remember we want to tell users they are on a blog page. Let's move our ``p { "-- Dioxus Blog --" }`` inside of our ``/blog`` route.
|
|
||||||
```rs
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
Router {
|
|
||||||
self::navbar {}
|
|
||||||
Route { to: "/", self::homepage {}}
|
|
||||||
Route {
|
|
||||||
to: "/blog",
|
|
||||||
p { "-- Dioxus Blog --" } // MOVED
|
|
||||||
Route { to: "/:post", "This is my blog post!" }
|
|
||||||
}
|
|
||||||
Route { to: "", self::page_not_found {}}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Now our ``-- Dioxus Blog --`` text will be displayed whenever a user is on a path that starts with ``/blog``. Displaying content in a way that is page-agnostic is useful when building navigation menus, footers, and similar.
|
|
||||||
|
|
||||||
All that's left is to handle our URL parameter. We will begin by creating a ``get_blog_post`` function. In a real site, this function would call an API endpoint to get a blog post from the database. However, that is out of the scope of this guide so we will be utilizing static text.
|
|
||||||
```rs
|
|
||||||
fn get_blog_post(id: &str) -> String {
|
|
||||||
match id {
|
|
||||||
"foo" => "Welcome to the foo blog post!".to_string(),
|
|
||||||
"bar" => "This is the bar blog post!".to_string(),
|
|
||||||
id => format!("Blog post '{id}' does not exist!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
Now that we have established our helper function, lets create a new ``blog_post`` component.
|
|
||||||
```rs
|
|
||||||
fn blog_post(cx: Scope) -> Element {
|
|
||||||
let blog_text = "";
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
|
||||||
p { "{blog_text}" }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
All that's left is to extract the blog id from the URL and to call our helper function to get the blog text. To do this we need to utilize Dioxus Router's ``use_route`` hook.
|
|
||||||
First start by adding ``use_route`` to your imports and then utilize the hook in your ``blog_post`` component.
|
|
||||||
```rs
|
|
||||||
use dioxus::{
|
|
||||||
prelude::*,
|
|
||||||
router::{use_route, Link, Route, Router}, // UPDATED
|
|
||||||
};
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
fn blog_post(cx: Scope) -> Element {
|
|
||||||
let route = use_route(cx); // NEW
|
|
||||||
let blog_text = "";
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
|
||||||
p { "{blog_text}" }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Dioxus Router provides built in methods to extract information from a route. We could utilize the ``segments``, ``nth_segment``, or ``last_segment`` method for our case but we'll use the ``segment`` method which extracts a specific URL parameter.
|
|
||||||
The ``segment`` method also parses the parameter into any type for us. We'll use a match expression that handles a parsing error and on success, uses our helper function to grab the blog post.
|
|
||||||
```rs
|
|
||||||
fn blog_post(cx: Scope) -> Element {
|
|
||||||
let route = use_route(cx);
|
|
||||||
|
|
||||||
// NEW
|
|
||||||
let blog_text = match route.segment::<String>("post").unwrap() {
|
|
||||||
Ok(val) => get_blog_post(&val),
|
|
||||||
Err(_) => "An unknown error occured".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
|
||||||
p { "{blog_text}" }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
And finally add the ``blog_post`` component to your ``app`` component:
|
|
||||||
```rs
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
Router {
|
|
||||||
self::navbar {}
|
|
||||||
Route { to: "/", self::homepage {}}
|
|
||||||
Route {
|
|
||||||
to: "/blog",
|
|
||||||
p { "-- Dioxus Blog --" }
|
|
||||||
Route { to: "/:post", self::blog_post {} } // UPDATED
|
|
||||||
}
|
|
||||||
Route { to: "", self::page_not_found {}}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
That's it! If you head to ``/blog/foo`` you should see ``Welcome to the foo blog post!``.
|
|
||||||
|
|
||||||
### Conclusion
|
|
||||||
In this chapter we utilized Dioxus Router's Link, URL Parameter, and ``use_route`` functionality to build the blog portion of our application. In the next and final chapter, we will go over the ``Redirect`` component to redirect non-authorized users to another page.
|
|
|
@ -1,95 +0,0 @@
|
||||||
# Creating Our First Route
|
|
||||||
In this chapter, we will continue off of our new Dioxus project to create a homepage and start utilizing Dioxus Router!
|
|
||||||
|
|
||||||
### Fundamentals
|
|
||||||
Dioxus Router works based on a router and route component. If you've ever used [React Router](https://reactrouter.com/), you should feel at home with Dioxus Router.
|
|
||||||
|
|
||||||
To get started, import the ``Router`` and ``Route`` components.
|
|
||||||
```rs
|
|
||||||
use dioxus::{
|
|
||||||
prelude::*,
|
|
||||||
router::{Route, Router}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
We also need an actual page to route to! Add a homepage component:
|
|
||||||
```rs
|
|
||||||
fn homepage(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
p { "Welcome to Dioxus Blog!" }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### To Route or Not to Route
|
|
||||||
We want to use Dioxus Router to seperate our application into different "pages". Dioxus Router will then determine which page to render based on the URL path.
|
|
||||||
|
|
||||||
To start using Dioxus Router, we need to use the ``Router`` component.
|
|
||||||
Replace the ``p { "Hello, wasm!" }`` in your ``app`` component with a Router component:
|
|
||||||
```rs
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
Router {} // NEW
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Now we have established a router and we can create our first route. We will be creating a route for our homepage component we created earlier.
|
|
||||||
```rs
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
Router {
|
|
||||||
Route { to: "/", self::homepage {}} // NEW
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
If you head to your application's browser tab, you should see the text ``Welcome to Dioxus Blog!`` when on the root URL (``http://localhost:8080/``). If you enter a different path for the URL, nothing should be displayed.
|
|
||||||
|
|
||||||
This is because we told Dioxus Router to render the ``homepage`` component only when the URL path is ``/``. You can tell Dioxus Router to render any kind of component such as a ``div {}``.
|
|
||||||
|
|
||||||
### What if a Route Doesn't Exist?
|
|
||||||
In our example Dioxus Router doesn't render anything. If we wanted to, we could tell Dioxus Router to render a component all the time! Try it out:
|
|
||||||
```rs
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
Router {
|
|
||||||
p { "-- Dioxus Blog --" } // NEW
|
|
||||||
Route { to: "/", self::homepage {}}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
We will go into more detail about this in the next chapter.
|
|
||||||
|
|
||||||
Many sites also have a "404" page for when a URL path leads to nowhere. Dioxus Router can do this too! Create a new ``page_not_found`` component.
|
|
||||||
```rs
|
|
||||||
fn page_not_found(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
p { "Oops! The page you are looking for doesn't exist!" }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now to tell Dioxus Router to render our new component when no route exists. Create a new route with a path of nothing:
|
|
||||||
```rs
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
Router {
|
|
||||||
p { "-- Dioxus Blog --" }
|
|
||||||
Route { to: "/", self::homepage {}}
|
|
||||||
Route { to: "", self::page_not_found {}} // NEW
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Now when you go to a route that doesn't exist, you should see the page not found text and the text we told Dioxus Router to render all the time.
|
|
||||||
```
|
|
||||||
// localhost:8080/abc
|
|
||||||
|
|
||||||
-- Dioxus Blog --
|
|
||||||
Oops! The page you are looking for doesn't exist!
|
|
||||||
```
|
|
||||||
|
|
||||||
> Make sure you put your empty route at the bottom or else it'll override any routes below it!
|
|
||||||
|
|
||||||
### Conclusion
|
|
||||||
In this chapter we learned how to create a route and tell Dioxus Router what component to render when the URL path is equal to what we specified. We also created a 404 page to handle when a route doesn't exist. Next, we'll create the blog portion of our site. We will utilize nested routes and URL parameters.
|
|
|
@ -1,68 +0,0 @@
|
||||||
# Getting Started
|
|
||||||
Before we start utilizing Dioxus Router, we need to initialize a Dioxus web application.
|
|
||||||
|
|
||||||
#### Required Tools
|
|
||||||
If you haven't already, make sure you install the [dioxus-cli](https://dioxuslabs.com/nightly/cli/) build tool and the rust ``wasm32-unknown-unknown`` target:
|
|
||||||
```
|
|
||||||
$ cargo install dioxus-cli
|
|
||||||
...
|
|
||||||
$ rustup target add wasm32-unkown-unknown
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating the Project
|
|
||||||
First, create a new cargo binary project:
|
|
||||||
```
|
|
||||||
cargo new --bin dioxus-blog
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, we need to add dioxus with the web and router feature to our ``Cargo.toml`` file.
|
|
||||||
```toml
|
|
||||||
[package]
|
|
||||||
name = "dioxus-blog"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
dioxus = { version = "0.1.8", features = ["web", "router"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we can start coding! Create an ``index.html`` file in the root of your project:
|
|
||||||
```html
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Dioxus Blog</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="main"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
You can add whatever you want to this file, just ensure that you have a ``div`` with the id of ``main`` in the root of your body element. This is essentially a handle to where Dioxus will render your components.
|
|
||||||
|
|
||||||
Now move to ``src/main.rs`` and replace its contents with:
|
|
||||||
```rs
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// Launch Dioxus web app
|
|
||||||
dioxus_web::launch(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Our root component.
|
|
||||||
fn app(cx: Scope) -> Element {
|
|
||||||
// Render "Hello, wasm!" to the screen.
|
|
||||||
cx.render(rsx! {
|
|
||||||
p { "Hello, wasm!"}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Our project is now setup! To make sure everything is running correctly, in the root of your project run:
|
|
||||||
```
|
|
||||||
dioxus serve --platform web
|
|
||||||
```
|
|
||||||
Then head to [http://localhost:8080](http://localhost:8080) in your browser, and you should see ``Hello, wasm!`` on your screen.
|
|
||||||
|
|
||||||
#### Conclusion
|
|
||||||
We setup a new project with Dioxus and got everything running correctly. Next we'll create a small homepage and start our journey with Dioxus Router.
|
|
|
@ -1,14 +0,0 @@
|
||||||
# Dioxus Router: Guide
|
|
||||||
In this guide you'll learn to effectively use Dioxus Router whether you're building a small todo app or the next FAANG company. We will create a small website with a blog, homepage, and more!
|
|
||||||
|
|
||||||
#### You'll learn how to
|
|
||||||
- Create routes and render "pages".
|
|
||||||
- Utilize nested routes, create a navigation bar, and render content for a set of routes.
|
|
||||||
- Gather URL parameters to dynamically display content.
|
|
||||||
- Redirect your visitors wherever you want.
|
|
||||||
|
|
||||||
> Disclaimer
|
|
||||||
>
|
|
||||||
> This site will only display the features of Dioxus Router. It will not include any actual functionality. To keep things simple we will only be using a single file, this is not the recommended way of doing things with a real application.
|
|
||||||
|
|
||||||
You can find the complete application [here](https://github.com/DogeDark/dioxus-router-example).
|
|
|
@ -1,51 +0,0 @@
|
||||||
# Redirection Perfection
|
|
||||||
You're well on your way to becoming a routing master!
|
|
||||||
|
|
||||||
In this chapter we will cover utilizing the ``Redirect`` component so you can take Rickrolling to the next level. We will also provide some optional challenges at the end if you want to continue your practice with not only Dioxus Router but with Dioxus in general.
|
|
||||||
|
|
||||||
### What Is This Redirect Thing?
|
|
||||||
The ``Redirect`` component is simple! When Dioxus determines that it should be rendered, it will redirect your application visitor to wherever you want.
|
|
||||||
In this example, let's say that you added a secret page to your site but didn't have time to program in the permission system. As a quick fix you add a redirect.
|
|
||||||
|
|
||||||
As always, let's first create a new component named ``secret_page``.
|
|
||||||
```rs
|
|
||||||
fn secret_page(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
p { "This page is not to be viewed!" }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
To redirect our visitors, all we have to do is render the ``Redirect`` component. The ``Redirect`` component is very similar to the ``Link`` component. The main difference is it doesn't display anything new.
|
|
||||||
First import the ``Redirect`` component and then update your ``secret_page`` component:
|
|
||||||
```rs
|
|
||||||
use dioxus::{
|
|
||||||
prelude::*,
|
|
||||||
router::{use_route, Link, Redirect, Route, Router}, // UPDATED
|
|
||||||
};
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
fn secret_page(cx: Scope) -> Element {
|
|
||||||
cx.render(rsx! {
|
|
||||||
p { "This page is not to be viewed!" }
|
|
||||||
Redirect { to: "/" } // NEW
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
That's it! Now your users will be redirected away from the secret page.
|
|
||||||
|
|
||||||
>Similar to the ``Link`` component, the ``Redirect`` component needs to be explicitly set to redirect to an external site. To link to external sites, add the ``external: true`` property.
|
|
||||||
>```rs
|
|
||||||
>Redirect { to: "https://github.com", external: true}
|
|
||||||
>```
|
|
||||||
|
|
||||||
### Conclusion
|
|
||||||
Well done! You've completed the Dioxus Router guide book. You've built a small application and learned about the many things you can do with Dioxus Router. To continue your journey, you can find a list of challenges down below, or you can check out the [reference](../reference/index.md).
|
|
||||||
|
|
||||||
### Challenges
|
|
||||||
- Organize your components into seperate files for better maintainability.
|
|
||||||
- Give your app some style if you haven't already.
|
|
||||||
- Build an about page so your visitors know who you are.
|
|
||||||
- Add a user system that uses URL parameters.
|
|
||||||
- Create a simple admin system to create, delete, and edit blogs.
|
|
||||||
- If you want to go to the max, hook up your application to a rest API and database.
|
|
27
docs/router/src/index.md
Normal file
27
docs/router/src/index.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
> If you are not familiar with Dioxus itself, check out the [Dioxus book](https://dioxuslabs.com/docs/0.3/guide/en/) first.
|
||||||
|
|
||||||
|
Whether you are building a website, desktop app, or mobile app,
|
||||||
|
splitting your app's views into "pages" can be an effective method for
|
||||||
|
organization and maintainability.
|
||||||
|
|
||||||
|
For this purpose, Dioxus provides a router. Use the `cargo add` command to add the dependency:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo add dioxus-router
|
||||||
|
```
|
||||||
|
|
||||||
|
This book is intended to get you up to speed with Dioxus Router. It is split
|
||||||
|
into two sections:
|
||||||
|
|
||||||
|
1. The [reference](./reference/index.md) section explains individual features in
|
||||||
|
depth. You can read it from start to finish, or you can read individual chapters
|
||||||
|
in whatever order you want.
|
||||||
|
2. If you prefer a learning-by-doing approach, you can check out the
|
||||||
|
_[example project](./example/index.md)_. It guides you through
|
||||||
|
creating a dioxus app, setting up the router, and using some of its
|
||||||
|
functionality.
|
||||||
|
|
||||||
|
> Please note that this is not the only documentation for the Dioxus Router. You
|
||||||
|
> can also check out the [API Docs](https://docs.rs/dioxus-router/).
|
1
docs/router/src/lib.rs
Normal file
1
docs/router/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
// empty (we only need this crate for the examples)
|
32
docs/router/src/reference/history-buttons.md
Normal file
32
docs/router/src/reference/history-buttons.md
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# History Buttons
|
||||||
|
|
||||||
|
Some platforms, like web browsers, provide users with an easy way to navigate
|
||||||
|
through an app's history. They have UI elements or integrate with the OS.
|
||||||
|
|
||||||
|
However, native platforms usually don't provide such amenities, which means that
|
||||||
|
apps wanting users to have access to them, need to implement them. For this
|
||||||
|
reason, the router comes with two components, which emulate a browser's back and
|
||||||
|
forward buttons:
|
||||||
|
|
||||||
|
- [`GoBackButton`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoBackButton.html)
|
||||||
|
- [`GoForwardButton`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoForwardButton.html)
|
||||||
|
|
||||||
|
> If you want to navigate through the history programmatically, take a look at
|
||||||
|
> [`programmatic navigation`](./navigation/programmatic.md).
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/history_buttons.rs:history_buttons}}
|
||||||
|
```
|
||||||
|
|
||||||
|
As you might know, browsers usually disable the back and forward buttons if
|
||||||
|
there is no history to navigate to. The router's history buttons try to do that
|
||||||
|
too, but depending on the [history provider] that might not be possible.
|
||||||
|
|
||||||
|
Importantly, neither [`WebHistory`] supports that feature.
|
||||||
|
This is due to limitations of the browser History API.
|
||||||
|
|
||||||
|
However, in both cases, the router will just ignore button presses, if there is
|
||||||
|
no history to navigate to.
|
||||||
|
|
||||||
|
Also, when using [`WebHistory`], the history buttons might
|
||||||
|
navigate a user to a history entry outside your app.
|
20
docs/router/src/reference/history-providers.md
Normal file
20
docs/router/src/reference/history-providers.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# History Providers
|
||||||
|
|
||||||
|
[`HistoryProvider`]s are used by the router to keep track of the navigation history
|
||||||
|
and update any external state (e.g. the browser's URL).
|
||||||
|
|
||||||
|
The router provides two [`HistoryProvider`]s, but you can also create your own.
|
||||||
|
The two default implementations are:
|
||||||
|
|
||||||
|
- The [`MemoryHistory`] is a custom implementation that works in memory.
|
||||||
|
- The [`WebHistory`] integrates with the browser's URL.
|
||||||
|
|
||||||
|
By default, the router uses the [`MemoryHistory`]. It might be changed to use
|
||||||
|
[`WebHistory`] when the `web` feature is active, but that is not guaranteed.
|
||||||
|
|
||||||
|
You can override the default history:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/history_provider.rs:app}}
|
||||||
|
```
|
||||||
|
|
|
@ -1,2 +1,23 @@
|
||||||
# Dioxus Router: Reference
|
# Adding the Router to Your Application
|
||||||
This section includes a reference to Dioxus Router's API and functionality.
|
|
||||||
|
In this chapter, we will learn how to add the router to our app. By itself, this
|
||||||
|
is not very useful. However, it is a prerequisite for all the functionality
|
||||||
|
described in the other chapters.
|
||||||
|
|
||||||
|
> Make sure you added the `dioxus-router` dependency as explained in the
|
||||||
|
> [introduction](../index.md).
|
||||||
|
|
||||||
|
In most cases, we want to add the router to the root component of our app. This
|
||||||
|
way, we can ensure that we have access to all its functionality everywhere.
|
||||||
|
|
||||||
|
First, we define the router with the router macro:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/first_route.rs:router}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then we render the router with the [`Router`] component.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/first_route.rs:app}}
|
||||||
|
```
|
||||||
|
|
19
docs/router/src/reference/layouts.md
Normal file
19
docs/router/src/reference/layouts.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Layouts
|
||||||
|
|
||||||
|
Layouts allow you to wrap all child routes in a component. This can be useful when creating something like a header that will be used in many different routes.
|
||||||
|
|
||||||
|
[`Outlet`] tells the router where to render content in layouts. In the following example,
|
||||||
|
the Index will be rendered within the [`Outlet`].
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/outlet.rs:outlet}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The example above will output the following HTML (line breaks added for
|
||||||
|
readability):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<header>header</header>
|
||||||
|
<h1>Index</h1>
|
||||||
|
<footer>footer</footer>
|
||||||
|
```
|
39
docs/router/src/reference/navigation/index.md
Normal file
39
docs/router/src/reference/navigation/index.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Links & Navigation
|
||||||
|
|
||||||
|
When we split our app into pages, we need to provide our users with a way to
|
||||||
|
navigate between them. On regular web pages, we'd use an anchor element for that,
|
||||||
|
like this:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<a href="/other">Link to an other page</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
However, we cannot do that when using the router for three reasons:
|
||||||
|
|
||||||
|
1. Anchor tags make the browser load a new page from the server. This takes a
|
||||||
|
lot of time, and it is much faster to let the router handle the navigation
|
||||||
|
client-side.
|
||||||
|
2. Navigation using anchor tags only works when the app is running inside a
|
||||||
|
browser. This means we cannot use them inside apps using Dioxus Desktop.
|
||||||
|
3. Anchor tags cannot check if the target page exists. This means we cannot
|
||||||
|
prevent accidentally linking to non-existent pages.
|
||||||
|
|
||||||
|
To solve these problems, the router provides us with a [`Link`] component we can
|
||||||
|
use like this:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../../examples/links.rs:nav}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `target` in the example above is similar to the `href` of a regular anchor
|
||||||
|
element. However, it tells the router more about what kind of navigation it
|
||||||
|
should perform. It accepts something that can be converted into a
|
||||||
|
[`NavigationTarget`]:
|
||||||
|
|
||||||
|
- The example uses a Internal route. This is the most common type of navigation.
|
||||||
|
It tells the router to navigate to a page within our app by passing a variant of a [`Routable`] enum. This type of navigation can never fail if the link component is used inside a router component.
|
||||||
|
- [`External`] allows us to navigate to URLs outside of our app. This is useful
|
||||||
|
for links to external websites. NavigationTarget::External accepts an URL to navigate to. This type of navigation can fail if the URL is invalid.
|
||||||
|
|
||||||
|
> The [`Link`] accepts several props that modify its behavior. See the API docs
|
||||||
|
> for more details.
|
32
docs/router/src/reference/navigation/programmatic.md
Normal file
32
docs/router/src/reference/navigation/programmatic.md
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# Programmatic Navigation
|
||||||
|
|
||||||
|
Sometimes we want our application to navigate to another page without having the
|
||||||
|
user click on a link. This is called programmatic navigation.
|
||||||
|
|
||||||
|
## Using a Navigator
|
||||||
|
|
||||||
|
We can get a navigator with the [`use_navigator`] hook. This hook returns a [`Navigator`].
|
||||||
|
|
||||||
|
We can use the [`Navigator`] to trigger four different kinds of navigation:
|
||||||
|
|
||||||
|
- `push` will navigate to the target. It works like a regular anchor tag.
|
||||||
|
- `replace` works like `push`, except that it replaces the current history entry
|
||||||
|
instead of adding a new one. This means the prior page cannot be restored with the browser's back button.
|
||||||
|
- `Go back` works like the browser's back button.
|
||||||
|
- `Go forward` works like the browser's forward button.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../../examples/navigator.rs:nav}}
|
||||||
|
```
|
||||||
|
|
||||||
|
You might have noticed that, like [`Link`], the [`Navigator`]s `push` and
|
||||||
|
`replace` functions take a [`NavigationTarget`]. This means we can use either
|
||||||
|
[`Internal`], or [`External`] targets.
|
||||||
|
|
||||||
|
## External Navigation Targets
|
||||||
|
|
||||||
|
Unlike a [`Link`], the [`Navigator`] cannot rely on the browser (or webview) to
|
||||||
|
handle navigation to external targets via a generated anchor element.
|
||||||
|
|
||||||
|
This means, that under certain conditions, navigation to external targets can
|
||||||
|
fail.
|
13
docs/router/src/reference/redirects.md
Normal file
13
docs/router/src/reference/redirects.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Redirects
|
||||||
|
|
||||||
|
In some cases, we may want to redirect our users to another page whenever they
|
||||||
|
open a specific path. We can tell the router to do this with the `#[redirect]`
|
||||||
|
attribute.
|
||||||
|
|
||||||
|
The `#[redirect]` attribute accepts a route and a closure with all of the parameters defined in the route. The closure must return a [`NavigationTarget`].
|
||||||
|
|
||||||
|
In the following example, we will redirect everybody from `/myblog` and `/myblog/:id` to `/blog` and `/blog/:id` respectively
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/full_example.rs:router}}
|
||||||
|
```
|
65
docs/router/src/reference/routes/index.md
Normal file
65
docs/router/src/reference/routes/index.md
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
# Defining Routes
|
||||||
|
|
||||||
|
When creating a [`Routable`] enum, we can define routes for our application using the `route("path")` attribute.
|
||||||
|
|
||||||
|
## Route Segments
|
||||||
|
|
||||||
|
Each route is made up of segments. Most segments are separated by `/` characters in the path.
|
||||||
|
|
||||||
|
There are four fundamental types of segments:
|
||||||
|
|
||||||
|
1. [Static segments](#static-segments) are fixed strings that must be present in the path.
|
||||||
|
2. [Dynamic segments](#dynamic-segments) are types that can be parsed from a segment.
|
||||||
|
3. [Catch-all segments](#catch-all-segments) are types that can be parsed from multiple segments.
|
||||||
|
4. [Query segments](#query-segments) are types that can be parsed from the query string.
|
||||||
|
|
||||||
|
Routes are matched:
|
||||||
|
|
||||||
|
- First, from most specific to least specific (Static then Dynamic then Catch All) (Query is always matched)
|
||||||
|
- Then, if multiple routes match the same path, the order in which they are defined in the enum is followed.
|
||||||
|
|
||||||
|
## Static segments
|
||||||
|
|
||||||
|
Fixed routes match a specific path. For example, the route `#[route("/about")]` will match the path `/about`.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../../examples/static_segments.rs:route}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dynamic Segments
|
||||||
|
|
||||||
|
Dynamic segments are in the form of `:name` where `name` is
|
||||||
|
the name of the field in the route variant. If the segment is parsed
|
||||||
|
successfully then the route matches, otherwise the matching continues.
|
||||||
|
|
||||||
|
The segment can be of any type that implements `FromStr`.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../../examples/dynamic_segments.rs:route}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Catch All Segments
|
||||||
|
|
||||||
|
Catch All segments are in the form of `:...name` where `name` is the name of the field in the route variant. If the segments are parsed successfully then the route matches, otherwise the matching continues.
|
||||||
|
|
||||||
|
The segment can be of any type that implements `FromSegments`. (Vec<String> implements this by default)
|
||||||
|
|
||||||
|
Catch All segments must be the _last route segment_ in the path (query segments are not counted) and cannot be included in nests.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../../examples/catch_all_segments.rs:route}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Query Segments
|
||||||
|
|
||||||
|
Query segments are in the form of `?:name` where `name` is the name of the field in the route variant.
|
||||||
|
|
||||||
|
Unlike [Dynamic Segments](#dynamic-segments) and [Catch All Segments](#catch-all-segments), parsing a Query segment must not fail.
|
||||||
|
|
||||||
|
The segment can be of any type that implements `FromQuery`.
|
||||||
|
|
||||||
|
Query segments must be the _after all route segments_ and cannot be included in nests.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../../examples/query_segments.rs:route}}
|
||||||
|
```
|
39
docs/router/src/reference/routes/nested.md
Normal file
39
docs/router/src/reference/routes/nested.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Nested Routes
|
||||||
|
|
||||||
|
When developing bigger applications we often want to nest routes within each
|
||||||
|
other. As an example, we might want to organize a settings menu using this
|
||||||
|
pattern:
|
||||||
|
|
||||||
|
```plain
|
||||||
|
└ Settings
|
||||||
|
├ General Settings (displayed when opening the settings)
|
||||||
|
├ Change Password
|
||||||
|
└ Privacy Settings
|
||||||
|
```
|
||||||
|
|
||||||
|
We might want to map this structure to these paths and components:
|
||||||
|
|
||||||
|
```plain
|
||||||
|
/settings -> Settings { GeneralSettings }
|
||||||
|
/settings/password -> Settings { PWSettings }
|
||||||
|
/settings/privacy -> Settings { PrivacySettings }
|
||||||
|
```
|
||||||
|
|
||||||
|
Nested routes allow us to do this without repeating /settings in every route.
|
||||||
|
|
||||||
|
## Nesting
|
||||||
|
|
||||||
|
To nest routes, we use the `#[nest("path")]` and `#[end_nest]` attributes.
|
||||||
|
|
||||||
|
The path in nest must not:
|
||||||
|
|
||||||
|
1. Contain a [Catch All Segment](index.md#catch-all-segments)
|
||||||
|
2. Contain a [Query Segment](index.md#query-segments)
|
||||||
|
|
||||||
|
If you define a dynamic segment in a nest, it will be available to all child routes and layouts.
|
||||||
|
|
||||||
|
To finish a nest, we use the `#[end_nest]` attribute or the end of the enum.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../../examples/nest.rs:route}}
|
||||||
|
```
|
25
docs/router/src/reference/routing-update-callback.md
Normal file
25
docs/router/src/reference/routing-update-callback.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Routing Update Callback
|
||||||
|
|
||||||
|
In some cases, we might want to run custom code when the current route changes.
|
||||||
|
For this reason, the [`RouterConfig`] exposes an `on_update` field.
|
||||||
|
|
||||||
|
## How does the callback behave?
|
||||||
|
|
||||||
|
The `on_update` is called whenever the current routing information changes. It
|
||||||
|
is called after the router updated its internal state, but before dependent components and hooks are updated.
|
||||||
|
|
||||||
|
If the callback returns a [`NavigationTarget`], the router will replace the
|
||||||
|
current location with the specified target. It will not call the
|
||||||
|
`on_update` again.
|
||||||
|
|
||||||
|
If at any point the router encounters a
|
||||||
|
[navigation failure](./failures/index.md), it will go to the appropriate state
|
||||||
|
without calling the `on_update`. It doesn't matter if the invalid target
|
||||||
|
initiated the navigation, was found as a redirect target, or was returned by the
|
||||||
|
`on_update` itself.
|
||||||
|
|
||||||
|
## Code Example
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/routing_update.rs:router}}
|
||||||
|
```
|
15
docs/router/src/reference/static-generation.md
Normal file
15
docs/router/src/reference/static-generation.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Static Generation
|
||||||
|
|
||||||
|
## Getting the Sitemap
|
||||||
|
|
||||||
|
The [`Routable`] trait includes an associated [`SITE_MAP`] constant that contains the map of all of the routes in the enum.
|
||||||
|
|
||||||
|
By default, the sitemap is a tree of (static or dynamic) RouteTypes, but it can be flattened into a list of individual routes with the `.flatten()` method.
|
||||||
|
|
||||||
|
## Generating a Sitemap
|
||||||
|
|
||||||
|
To statically render pages, we need to flatten the route tree and generate a file for each route that contains only static segments:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../../../packages/router/examples/static_generation.rs}}
|
||||||
|
```
|
|
@ -9,8 +9,8 @@ It is also very much usable as a template for your projects, if you're aiming to
|
||||||
|
|
||||||
Make sure you have Dioxus CLI installed (if you're unsure, run `cargo install dioxus-cli`).
|
Make sure you have Dioxus CLI installed (if you're unsure, run `cargo install dioxus-cli`).
|
||||||
|
|
||||||
You can run `dioxus serve` in this directory to start the web server locally, or run
|
You can run `dx serve` in this directory to start the web server locally, or run
|
||||||
`dioxus build --release` to build the project so you can deploy it on a separate web-server.
|
`dx build --release` to build the project so you can deploy it on a separate web-server.
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
```
|
```
|
||||||
|
|
224
examples/crm.rs
224
examples/crm.rs
|
@ -1,11 +1,21 @@
|
||||||
/*
|
//! Tiny CRM: A port of the Yew CRM example to Dioxus.
|
||||||
Tiny CRM: A port of the Yew CRM example to Dioxus.
|
#![allow(non_snake_case)]
|
||||||
*/
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_router::{Link, Route, Router};
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus_desktop::launch(app);
|
dioxus_desktop::launch(App);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[route("/")]
|
||||||
|
ClientList {},
|
||||||
|
#[route("/new")]
|
||||||
|
ClientAdd {},
|
||||||
|
#[route("/settings")]
|
||||||
|
Settings {},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
@ -15,92 +25,174 @@ pub struct Client {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
type ClientContext = Vec<Client>;
|
||||||
let clients = use_ref(cx, || vec![] as Vec<Client>);
|
|
||||||
let firstname = use_state(cx, String::new);
|
|
||||||
let lastname = use_state(cx, String::new);
|
|
||||||
let description = use_state(cx, String::new);
|
|
||||||
|
|
||||||
cx.render(rsx!(
|
fn App(cx: Scope) -> Element {
|
||||||
body {
|
use_shared_state_provider::<ClientContext>(cx, Default::default);
|
||||||
margin_left: "35%",
|
|
||||||
|
render! {
|
||||||
link {
|
link {
|
||||||
rel: "stylesheet",
|
rel: "stylesheet",
|
||||||
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css",
|
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css",
|
||||||
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
|
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
|
||||||
crossorigin: "anonymous",
|
crossorigin: "anonymous",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
style { "
|
||||||
|
.red {{
|
||||||
|
background-color: rgb(202, 60, 60) !important;
|
||||||
|
}}
|
||||||
|
" }
|
||||||
|
|
||||||
h1 { "Dioxus CRM Example" }
|
h1 { "Dioxus CRM Example" }
|
||||||
Router {
|
|
||||||
Route { to: "/",
|
Router {}
|
||||||
div { class: "crm",
|
}
|
||||||
h2 { margin_bottom: "10px", "List of clients" }
|
}
|
||||||
div { class: "clients", margin_left: "10px",
|
|
||||||
clients.read().iter().map(|client| rsx!(
|
#[inline_props]
|
||||||
div { class: "client", style: "margin-bottom: 50px",
|
fn ClientList(cx: Scope) -> Element {
|
||||||
p { "First Name: {client.first_name}" }
|
let clients = use_shared_state::<ClientContext>(cx).unwrap();
|
||||||
p { "Last Name: {client.last_name}" }
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
h2 { "List of Clients" }
|
||||||
|
|
||||||
|
Link {
|
||||||
|
target: Route::ClientAdd {},
|
||||||
|
class: "pure-button pure-button-primary",
|
||||||
|
"Add Client"
|
||||||
|
}
|
||||||
|
Link {
|
||||||
|
target: Route::Settings {},
|
||||||
|
class: "pure-button",
|
||||||
|
"Settings"
|
||||||
|
}
|
||||||
|
|
||||||
|
clients.read().iter().map(|client| rsx! {
|
||||||
|
div {
|
||||||
|
class: "client",
|
||||||
|
style: "margin-bottom: 50px",
|
||||||
|
|
||||||
|
p { "Name: {client.first_name} {client.last_name}" }
|
||||||
p { "Description: {client.description}" }
|
p { "Description: {client.description}" }
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Link { to: "/new", class: "pure-button pure-button-primary", "Add New" }
|
|
||||||
Link { to: "/settings", class: "pure-button", "Settings" }
|
#[inline_props]
|
||||||
}
|
fn ClientAdd(cx: Scope) -> Element {
|
||||||
}
|
let clients = use_shared_state::<ClientContext>(cx).unwrap();
|
||||||
Route { to: "/new",
|
let first_name = use_state(cx, String::new);
|
||||||
div { class: "crm",
|
let last_name = use_state(cx, String::new);
|
||||||
h2 { margin_bottom: "10px", "Add new client" }
|
let description = use_state(cx, String::new);
|
||||||
form { class: "pure-form",
|
|
||||||
input {
|
let navigator = use_navigator(cx);
|
||||||
class: "new-client firstname",
|
|
||||||
placeholder: "First name",
|
cx.render(rsx! {
|
||||||
value: "{firstname}",
|
h2 { "Add new Client" }
|
||||||
oninput: move |e| firstname.set(e.value.clone())
|
|
||||||
|
form {
|
||||||
|
class: "pure-form pure-form-aligned",
|
||||||
|
onsubmit: move |_| {
|
||||||
|
let mut clients = clients.write();
|
||||||
|
|
||||||
|
clients.push(Client {
|
||||||
|
first_name: first_name.to_string(),
|
||||||
|
last_name: last_name.to_string(),
|
||||||
|
description: description.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
navigator.push(Route::ClientList {});
|
||||||
|
},
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
div {
|
||||||
|
class: "pure-control-group",
|
||||||
|
label {
|
||||||
|
"for": "first_name",
|
||||||
|
"First Name"
|
||||||
}
|
}
|
||||||
input {
|
input {
|
||||||
class: "new-client lastname",
|
id: "first_name",
|
||||||
placeholder: "Last name",
|
"type": "text",
|
||||||
value: "{lastname}",
|
placeholder: "First Name…",
|
||||||
oninput: move |e| lastname.set(e.value.clone())
|
required: "",
|
||||||
|
value: "{first_name}",
|
||||||
|
oninput: move |e| first_name.set(e.value.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "pure-control-group",
|
||||||
|
label {
|
||||||
|
"for": "last_name",
|
||||||
|
"Last Name"
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
id: "last_name",
|
||||||
|
"type": "text",
|
||||||
|
placeholder: "Last Name…",
|
||||||
|
required: "",
|
||||||
|
value: "{last_name}",
|
||||||
|
oninput: move |e| last_name.set(e.value.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "pure-control-group",
|
||||||
|
label {
|
||||||
|
"for": "description",
|
||||||
|
"Description"
|
||||||
}
|
}
|
||||||
textarea {
|
textarea {
|
||||||
class: "new-client description",
|
id: "description",
|
||||||
placeholder: "Description",
|
placeholder: "Description…",
|
||||||
value: "{description}",
|
value: "{description}",
|
||||||
oninput: move |e| description.set(e.value.clone())
|
oninput: move |e| description.set(e.value.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
button {
|
|
||||||
class: "pure-button pure-button-primary",
|
|
||||||
onclick: move |_| {
|
|
||||||
clients.write().push(Client {
|
|
||||||
description: description.to_string(),
|
|
||||||
first_name: firstname.to_string(),
|
|
||||||
last_name: lastname.to_string(),
|
|
||||||
});
|
|
||||||
description.set(String::new());
|
|
||||||
firstname.set(String::new());
|
|
||||||
lastname.set(String::new());
|
|
||||||
},
|
|
||||||
"Add New"
|
|
||||||
}
|
|
||||||
Link { to: "/", class: "pure-button", "Go Back" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Route { to: "/settings",
|
|
||||||
div {
|
div {
|
||||||
h2 { margin_bottom: "10px", "Settings" }
|
class: "pure-controls",
|
||||||
button {
|
button {
|
||||||
background: "rgb(202, 60, 60)",
|
"type": "submit",
|
||||||
class: "pure-button pure-button-primary",
|
class: "pure-button pure-button-primary",
|
||||||
onclick: move |_| clients.write().clear(),
|
"Save"
|
||||||
"Remove all clients"
|
|
||||||
}
|
}
|
||||||
Link { to: "/", class: "pure-button pure-button-primary", "Go Back" }
|
Link {
|
||||||
|
target: Route::ClientList {},
|
||||||
|
class: "pure-button pure-button-primary red",
|
||||||
|
"Cancel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
))
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Settings(cx: Scope) -> Element {
|
||||||
|
let clients = use_shared_state::<ClientContext>(cx).unwrap();
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
h2 { "Settings" }
|
||||||
|
|
||||||
|
button {
|
||||||
|
class: "pure-button pure-button-primary red",
|
||||||
|
onclick: move |_| {
|
||||||
|
let mut clients = clients.write();
|
||||||
|
clients.clear();
|
||||||
|
},
|
||||||
|
"Remove all Clients"
|
||||||
|
}
|
||||||
|
|
||||||
|
Link {
|
||||||
|
target: Route::ClientList {},
|
||||||
|
class: "pure-button",
|
||||||
|
"Go back"
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ struct ListBreeds {
|
||||||
message: HashMap<String, Vec<String>>,
|
message: HashMap<String, Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn app_root(cx: Scope<'_>) -> Element {
|
fn app_root(cx: Scope<'_>) -> Element {
|
||||||
let breed = use_state(cx, || "deerhound".to_string());
|
let breed = use_state(cx, || "deerhound".to_string());
|
||||||
|
|
||||||
let breeds = use_future!(cx, || async move {
|
let breeds = use_future!(cx, || async move {
|
||||||
|
@ -21,13 +21,13 @@ async fn app_root(cx: Scope<'_>) -> Element {
|
||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
|
|
||||||
match breeds.await {
|
match breeds.value()? {
|
||||||
Ok(breeds) => cx.render(rsx! {
|
Ok(breed_list) => cx.render(rsx! {
|
||||||
div { height: "500px",
|
div { height: "500px",
|
||||||
h1 { "Select a dog breed!" }
|
h1 { "Select a dog breed!" }
|
||||||
div { display: "flex",
|
div { display: "flex",
|
||||||
ul { flex: "50%",
|
ul { flex: "50%",
|
||||||
for cur_breed in breeds.message.keys().take(10) {
|
for cur_breed in breed_list.message.keys().take(10) {
|
||||||
li { key: "{cur_breed}",
|
li { key: "{cur_breed}",
|
||||||
button {
|
button {
|
||||||
onclick: move |_| breed.set(cur_breed.clone()),
|
onclick: move |_| breed.set(cur_breed.clone()),
|
||||||
|
@ -50,7 +50,7 @@ struct DogApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline_props]
|
#[inline_props]
|
||||||
async fn breed_pic(cx: Scope, breed: String) -> Element {
|
fn breed_pic(cx: Scope, breed: String) -> Element {
|
||||||
let fut = use_future!(cx, |breed| async move {
|
let fut = use_future!(cx, |breed| async move {
|
||||||
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
|
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
|
||||||
.await
|
.await
|
||||||
|
@ -59,7 +59,7 @@ async fn breed_pic(cx: Scope, breed: String) -> Element {
|
||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
|
|
||||||
match fut.await {
|
match fut.value()? {
|
||||||
Ok(resp) => render! {
|
Ok(resp) => render! {
|
||||||
div {
|
div {
|
||||||
button {
|
button {
|
||||||
|
|
|
@ -7,11 +7,11 @@ fn main() {
|
||||||
dioxus_desktop::launch(app)
|
dioxus_desktop::launch(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
static NAME: Atom<String> = |_| "world".to_string();
|
static NAME: Atom<String> = Atom(|_| "world".to_string());
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
use_init_atom_root(cx);
|
use_init_atom_root(cx);
|
||||||
let name = use_read(cx, NAME);
|
let name = use_read(cx, &NAME);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div { "hello {name}!" }
|
div { "hello {name}!" }
|
||||||
|
@ -21,7 +21,7 @@ fn app(cx: Scope) -> Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Child(cx: Scope) -> Element {
|
fn Child(cx: Scope) -> Element {
|
||||||
let set_name = use_set(cx, NAME);
|
let set_name = use_set(cx, &NAME);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
button {
|
button {
|
||||||
|
@ -31,10 +31,10 @@ fn Child(cx: Scope) -> Element {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static NAMES: AtomRef<Vec<String>> = |_| vec!["world".to_string()];
|
static NAMES: AtomRef<Vec<String>> = AtomRef(|_| vec!["world".to_string()]);
|
||||||
|
|
||||||
fn ChildWithRef(cx: Scope) -> Element {
|
fn ChildWithRef(cx: Scope) -> Element {
|
||||||
let names = use_atom_ref(cx, NAMES);
|
let names = use_atom_ref(cx, &NAMES);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
|
|
|
@ -1,32 +1,47 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus_desktop::launch(App);
|
dioxus_desktop::launch(App);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
fn App(cx: Scope) -> Element {
|
||||||
|
let enable_directory_upload = use_state(cx, || false);
|
||||||
let files_uploaded: &UseRef<Vec<String>> = use_ref(cx, Vec::new);
|
let files_uploaded: &UseRef<Vec<String>> = use_ref(cx, Vec::new);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
|
label {
|
||||||
|
input {
|
||||||
|
r#type: "checkbox",
|
||||||
|
checked: "{enable_directory_upload}",
|
||||||
|
oninput: move |evt| {
|
||||||
|
enable_directory_upload.set(evt.value.parse().unwrap());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Enable directory upload"
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
r#type: "file",
|
r#type: "file",
|
||||||
accept: ".txt,.rs",
|
accept: ".txt,.rs",
|
||||||
multiple: true,
|
multiple: true,
|
||||||
|
directory: **enable_directory_upload,
|
||||||
onchange: |evt| {
|
onchange: |evt| {
|
||||||
to_owned![files_uploaded];
|
to_owned![files_uploaded];
|
||||||
async move {
|
async move {
|
||||||
if let Some(file_engine) = &evt.files {
|
if let Some(file_engine) = &evt.files {
|
||||||
let files = file_engine.files();
|
let files = file_engine.files();
|
||||||
for file_name in &files {
|
for file_name in files {
|
||||||
if let Some(file) = file_engine.read_file_to_string(file_name).await{
|
sleep(std::time::Duration::from_secs(1)).await;
|
||||||
files_uploaded.write().push(file);
|
files_uploaded.write().push(file_name);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
|
||||||
|
div { "progress: {files_uploaded.read().len()}" },
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
for file in files_uploaded.read().iter() {
|
for file in files_uploaded.read().iter() {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_desktop::{tao::dpi::LogicalSize, Config, WindowBuilder};
|
use dioxus_desktop::{tao::dpi::LogicalSize, Config, WindowBuilder};
|
||||||
use dioxus_router::{Link, Route, Router};
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
@ -15,24 +17,63 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
render! {
|
||||||
div {
|
Router {}
|
||||||
Router {
|
}
|
||||||
Route { to: "/", "Home" }
|
}
|
||||||
Route { to: "/games", "Games" }
|
|
||||||
Route { to: "/play", "Play" }
|
#[derive(Routable, Clone)]
|
||||||
Route { to: "/settings", "Settings" }
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[layout(Footer)]
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
#[route("/games")]
|
||||||
|
Games {},
|
||||||
|
#[route("/play")]
|
||||||
|
Play {},
|
||||||
|
#[route("/settings")]
|
||||||
|
Settings {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Footer(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
div {
|
||||||
|
Outlet { }
|
||||||
|
|
||||||
|
p {
|
||||||
|
"----"
|
||||||
|
}
|
||||||
|
|
||||||
p { "----" }
|
|
||||||
nav {
|
nav {
|
||||||
ul {
|
ul {
|
||||||
Link { to: "/", li { "Home" } }
|
li { Link { target: Route::Home {}, "Home" } }
|
||||||
Link { to: "/games", li { "Games" } }
|
li { Link { target: Route::Games {}, "Games" } }
|
||||||
Link { to: "/play", li { "Play" } }
|
li { Link { target: Route::Play {}, "Play" } }
|
||||||
Link { to: "/settings", li { "Settings" } }
|
li { Link { target: Route::Settings {}, "Settings" } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render!("Home")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Games(cx: Scope) -> Element {
|
||||||
|
render!("Games")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Play(cx: Scope) -> Element {
|
||||||
|
render!("Play")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Settings(cx: Scope) -> Element {
|
||||||
|
render!("Settings")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_router::{Link, Route, Router};
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus_desktop::launch(app);
|
dioxus_desktop::launch(app);
|
||||||
|
@ -21,15 +23,39 @@ fn app(cx: Scope) -> Element {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
Router {
|
Router {}
|
||||||
Route { to: "/", h1 { "Home" } },
|
|
||||||
Route { to: "/settings", h1 { "settings" } },
|
|
||||||
p { "----"}
|
|
||||||
ul {
|
|
||||||
Link { to: "/", li { "Router link to home" } },
|
|
||||||
Link { to: "/settings", li { "Router link to settings" } },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[layout(Header)]
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
#[route("/settings")]
|
||||||
|
Settings {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Header(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Your app here" }
|
||||||
|
ul {
|
||||||
|
li { Link { target: Route::Home {}, "home" } }
|
||||||
|
li { Link { target: Route::Settings {}, "settings" } }
|
||||||
|
}
|
||||||
|
Outlet {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render!(h1 { "Home" })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Settings(cx: Scope) -> Element {
|
||||||
|
render!(h1 { "Settings" })
|
||||||
|
}
|
||||||
|
|
|
@ -1,67 +1,112 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_router::{Link, Route, Router};
|
use dioxus_router::prelude::*;
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus_desktop::launch(app);
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
dioxus_web::launch(App);
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
dioxus_desktop::launch(App);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
// ANCHOR: router
|
||||||
cx.render(rsx! {
|
#[derive(Routable, Clone)]
|
||||||
Router {
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[layout(NavBar)]
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
#[nest("/blog")]
|
||||||
|
#[layout(Blog)]
|
||||||
|
#[route("/")]
|
||||||
|
BlogList {},
|
||||||
|
#[route("/blog/:name")]
|
||||||
|
BlogPost { name: String },
|
||||||
|
#[end_layout]
|
||||||
|
#[end_nest]
|
||||||
|
#[end_layout]
|
||||||
|
#[nest("/myblog")]
|
||||||
|
#[redirect("/", || Route::BlogList {})]
|
||||||
|
#[redirect("/:name", |name: String| Route::BlogPost { name })]
|
||||||
|
#[end_nest]
|
||||||
|
#[route("/:..route")]
|
||||||
|
PageNotFound {
|
||||||
|
route: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// ANCHOR_END: router
|
||||||
|
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn NavBar(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
nav {
|
||||||
ul {
|
ul {
|
||||||
Link { to: "/", li { "Go home!" } }
|
li { Link { target: Route::Home {}, "Home" } }
|
||||||
Link { to: "/users", li { "List all users" } }
|
li { Link { target: Route::BlogList {}, "Blog" } }
|
||||||
Link { to: "/blog", li { "Blog posts" } }
|
|
||||||
|
|
||||||
Link { to: "/users/bill", li { "List all users" } }
|
|
||||||
Link { to: "/blog/5", li { "Blog post 5" } }
|
|
||||||
}
|
}
|
||||||
Route { to: "/", "Home" }
|
|
||||||
Route { to: "/users", "User list" }
|
|
||||||
Route { to: "/users/:name", User {} }
|
|
||||||
Route { to: "/blog", "Blog list" }
|
|
||||||
Route { to: "/blog/:post", BlogPost {} }
|
|
||||||
Route { to: "", "Err 404 Route Not Found" }
|
|
||||||
}
|
}
|
||||||
})
|
Outlet {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn BlogPost(cx: Scope) -> Element {
|
#[inline_props]
|
||||||
let post = dioxus_router::use_route(cx).last_segment().unwrap();
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
cx.render(rsx! {
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
div {
|
}
|
||||||
h1 { "Reading blog post: {post}" }
|
}
|
||||||
p { "example blog post" }
|
|
||||||
}
|
#[inline_props]
|
||||||
})
|
fn Blog(cx: Scope) -> Element {
|
||||||
}
|
render! {
|
||||||
|
h1 { "Blog" }
|
||||||
#[derive(Deserialize)]
|
Outlet {}
|
||||||
struct Query {
|
}
|
||||||
bold: bool,
|
}
|
||||||
}
|
|
||||||
|
#[inline_props]
|
||||||
fn User(cx: Scope) -> Element {
|
fn BlogList(cx: Scope) -> Element {
|
||||||
let post = dioxus_router::use_route(cx).last_segment().unwrap();
|
render! {
|
||||||
|
h2 { "Choose a post" }
|
||||||
let query = dioxus_router::use_route(cx)
|
ul {
|
||||||
.query::<Query>()
|
li {
|
||||||
.unwrap_or(Query { bold: false });
|
Link {
|
||||||
|
target: Route::BlogPost { name: "Blog post 1".into() },
|
||||||
cx.render(rsx! {
|
"Read the first blog post"
|
||||||
div {
|
}
|
||||||
h1 { "Reading blog post: {post}" }
|
}
|
||||||
p { "example blog post" }
|
li {
|
||||||
|
Link {
|
||||||
if query.bold {
|
target: Route::BlogPost { name: "Blog post 2".into() },
|
||||||
rsx!{ b { "bold" } }
|
"Read the second blog post"
|
||||||
} else {
|
}
|
||||||
rsx!{ i { "italic" } }
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogPost(cx: Scope, name: String) -> Element {
|
||||||
|
render! {
|
||||||
|
h2 { "Blog Post: {name}"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Page not found" }
|
||||||
|
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
||||||
|
pre {
|
||||||
|
color: "red",
|
||||||
|
"log:\nattemped to navigate to: {route:?}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
77
examples/shared_state.rs
Normal file
77
examples/shared_state.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus_desktop::launch(App);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct CoolData {
|
||||||
|
data: HashMap<usize, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoolData {
|
||||||
|
pub fn new(data: HashMap<usize, String>) -> Self {
|
||||||
|
Self { data }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&self, id: &usize) -> Option<&String> {
|
||||||
|
self.data.get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, id: usize, data: String) {
|
||||||
|
self.data.insert(id, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub fn App(cx: Scope) -> Element {
|
||||||
|
use_shared_state_provider(cx, || CoolData::new(HashMap::from([
|
||||||
|
(0, "Hello, World!".to_string()),
|
||||||
|
(1, "Dioxus is amazing!".to_string())
|
||||||
|
])));
|
||||||
|
|
||||||
|
render!(
|
||||||
|
DataEditor {
|
||||||
|
id: 0
|
||||||
|
}
|
||||||
|
DataEditor {
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
DataView {
|
||||||
|
id: 0
|
||||||
|
}
|
||||||
|
DataView {
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn DataEditor(cx: Scope, id: usize) -> Element {
|
||||||
|
let cool_data = use_shared_state::<CoolData>(cx).unwrap().read();
|
||||||
|
|
||||||
|
let my_data = &cool_data.view(id).unwrap();
|
||||||
|
|
||||||
|
render!(p {
|
||||||
|
"{my_data}"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn DataView(cx: Scope, id: usize) -> Element {
|
||||||
|
let cool_data = use_shared_state::<CoolData>(cx).unwrap();
|
||||||
|
|
||||||
|
let oninput = |e: FormEvent| cool_data.write().set(*id, e.value.clone());
|
||||||
|
|
||||||
|
let cool_data = cool_data.read();
|
||||||
|
let my_data = &cool_data.view(id).unwrap();
|
||||||
|
|
||||||
|
render!(input {
|
||||||
|
oninput: oninput,
|
||||||
|
value: "{my_data}"
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_router::*;
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
simple_logger::SimpleLogger::new()
|
simple_logger::SimpleLogger::new()
|
||||||
.with_level(log::LevelFilter::Debug)
|
.with_level(log::LevelFilter::Debug)
|
||||||
.with_module_level("dioxus_router", log::LevelFilter::Trace)
|
|
||||||
.with_module_level("dioxus", log::LevelFilter::Trace)
|
.with_module_level("dioxus", log::LevelFilter::Trace)
|
||||||
.init()
|
.init()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -14,49 +13,69 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
render! {
|
||||||
Router {
|
Router {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[layout(NavBar)]
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
#[nest("/new")]
|
||||||
|
#[route("/")]
|
||||||
|
BlogList {},
|
||||||
|
#[route("/:post")]
|
||||||
|
BlogPost {
|
||||||
|
post: String,
|
||||||
|
},
|
||||||
|
#[end_nest]
|
||||||
|
#[route("/oranges")]
|
||||||
|
Oranges {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn NavBar(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
h1 { "Your app here" }
|
h1 { "Your app here" }
|
||||||
ul {
|
ul {
|
||||||
Link { to: "/", li { "home" } }
|
li { Link { target: Route::Home {}, "home" } }
|
||||||
Link { to: "/blog", li { "blog" } }
|
li { Link { target: Route::BlogList {}, "blog" } }
|
||||||
Link { to: "/blog/tim", li { "tims' blog" } }
|
li { Link { target: Route::BlogPost { post: "tim".into() }, "tims' blog" } }
|
||||||
Link { to: "/blog/bill", li { "bills' blog" } }
|
li { Link { target: Route::BlogPost { post: "bill".into() }, "bills' blog" } }
|
||||||
Link { to: "/blog/james",
|
li { Link { target: Route::BlogPost { post: "james".into() }, "james amazing' blog" } }
|
||||||
li { "james amazing' blog" }
|
|
||||||
}
|
}
|
||||||
Link { to: "/apples", li { "go to apples" } }
|
Outlet {}
|
||||||
}
|
}
|
||||||
Route { to: "/", Home {} }
|
|
||||||
Route { to: "/blog/", BlogList {} }
|
|
||||||
Route { to: "/blog/:id/", BlogPost {} }
|
|
||||||
Route { to: "/oranges", "Oranges are not apples!" }
|
|
||||||
Redirect { from: "/apples", to: "/oranges" }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
fn Home(cx: Scope) -> Element {
|
fn Home(cx: Scope) -> Element {
|
||||||
log::debug!("rendering home {:?}", cx.scope_id());
|
log::debug!("rendering home {:?}", cx.scope_id());
|
||||||
cx.render(rsx! { h1 { "Home" } })
|
render! { h1 { "Home" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
fn BlogList(cx: Scope) -> Element {
|
fn BlogList(cx: Scope) -> Element {
|
||||||
log::debug!("rendering blog list {:?}", cx.scope_id());
|
log::debug!("rendering blog list {:?}", cx.scope_id());
|
||||||
cx.render(rsx! { div { "Blog List" } })
|
render! { div { "Blog List" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn BlogPost(cx: Scope) -> Element {
|
#[inline_props]
|
||||||
let Some(id) = use_route(cx).segment("id") else {
|
fn BlogPost(cx: Scope, post: String) -> Element {
|
||||||
return cx.render(rsx! { div { "No blog post id" } })
|
log::debug!("rendering blog post {}", post);
|
||||||
};
|
|
||||||
|
|
||||||
log::debug!("rendering blog post {}", id);
|
render! {
|
||||||
|
|
||||||
cx.render(rsx! {
|
|
||||||
div {
|
div {
|
||||||
h3 { "blog post: {id:?}" }
|
h3 { "blog post: {post}" }
|
||||||
Link { to: "/blog/", "back to blog list" }
|
Link { target: Route::BlogList {}, "back to blog list" }
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Oranges(cx: Scope) -> Element {
|
||||||
|
render!("Oranges are not apples!")
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,7 @@ npx tailwindcss -i ./input.css -o ./public/tailwind.css --watch
|
||||||
- Run the following command in the root of the project to start the dioxus dev server:
|
- Run the following command in the root of the project to start the dioxus dev server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dioxus serve --hot-reload
|
dx serve --hot-reload
|
||||||
```
|
```
|
||||||
|
|
||||||
- Open the browser to http://localhost:8080
|
- Open the browser to http://localhost:8080
|
||||||
|
|
|
@ -18,7 +18,7 @@ There are plenty Rust Elm-like frameworks in the world - we were not interested
|
||||||
The `RSX` DSL is _barely_ a DSL. Rustaceans will find the DSL very similar to simply assembling nested structs, but without the syntactical overhead of "Default" everywhere or having to jump through hoops with the builder pattern. Between RSX, HTML, the Raw Factory API, and the NodeBuilder syntax, there's plenty of options to choose from.
|
The `RSX` DSL is _barely_ a DSL. Rustaceans will find the DSL very similar to simply assembling nested structs, but without the syntactical overhead of "Default" everywhere or having to jump through hoops with the builder pattern. Between RSX, HTML, the Raw Factory API, and the NodeBuilder syntax, there's plenty of options to choose from.
|
||||||
|
|
||||||
### What are the build times like? Why on earth would I choose Rust instead of JS/TS/Elm?
|
### What are the build times like? Why on earth would I choose Rust instead of JS/TS/Elm?
|
||||||
Dioxus builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times.
|
dx builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times.
|
||||||
|
|
||||||
### What about Yew/Seed/Sycamore/Dominator/Dodrio/Percy?
|
### What about Yew/Seed/Sycamore/Dominator/Dodrio/Percy?
|
||||||
- Yew and Seed use an Elm-like pattern and don't support SSR or any alternate rendering platforms
|
- Yew and Seed use an Elm-like pattern and don't support SSR or any alternate rendering platforms
|
||||||
|
|
|
@ -4,7 +4,7 @@ version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
description = "Autofomatter for Dioxus RSX"
|
description = "Autofomatter for Dioxus RSX"
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||||
homepage = "https://dioxuslabs.com"
|
homepage = "https://dioxuslabs.com"
|
||||||
keywords = ["dom", "ui", "gui", "react"]
|
keywords = ["dom", "ui", "gui", "react"]
|
||||||
|
@ -14,9 +14,9 @@ keywords = ["dom", "ui", "gui", "react"]
|
||||||
dioxus-rsx = { workspace = true }
|
dioxus-rsx = { workspace = true }
|
||||||
proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
|
proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
syn = { version = "2.0", features = ["full", "extra-traits", "visit"] }
|
||||||
serde = { version = "1.0.136", features = ["derive"] }
|
serde = { version = "1.0.136", features = ["derive"] }
|
||||||
prettyplease = { package = "prettier-please", version = "0.1.16", features = [
|
prettyplease = { package = "prettier-please", version = "0.2", features = [
|
||||||
"verbatim",
|
"verbatim",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|
|
@ -3,177 +3,23 @@
|
||||||
//! Returns all macros that match a pattern. You can use this information to autoformat them later
|
//! Returns all macros that match a pattern. You can use this information to autoformat them later
|
||||||
|
|
||||||
use proc_macro2::LineColumn;
|
use proc_macro2::LineColumn;
|
||||||
use syn::{Block, Expr, File, Item, Macro, Stmt};
|
use syn::{visit::Visit, File, Macro};
|
||||||
|
|
||||||
type CollectedMacro<'a> = &'a Macro;
|
type CollectedMacro<'a> = &'a Macro;
|
||||||
|
|
||||||
pub fn collect_from_file<'a>(file: &'a File, macros: &mut Vec<CollectedMacro<'a>>) {
|
pub fn collect_from_file<'a>(file: &'a File, macros: &mut Vec<CollectedMacro<'a>>) {
|
||||||
for item in file.items.iter() {
|
MacroCollector::visit_file(&mut MacroCollector { macros }, file);
|
||||||
collect_from_item(item, macros);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_from_item<'a>(item: &'a Item, macros: &mut Vec<CollectedMacro<'a>>) {
|
struct MacroCollector<'a, 'b> {
|
||||||
match item {
|
macros: &'a mut Vec<CollectedMacro<'b>>,
|
||||||
Item::Fn(f) => collect_from_block(&f.block, macros),
|
|
||||||
|
|
||||||
// Ignore macros if they're not rsx or render
|
|
||||||
Item::Macro(macro_) => {
|
|
||||||
if macro_.mac.path.segments[0].ident == "rsx"
|
|
||||||
|| macro_.mac.path.segments[0].ident == "render"
|
|
||||||
{
|
|
||||||
macros.push(¯o_.mac);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently disabled since we're not focused on autoformatting these
|
impl<'a, 'b> Visit<'b> for MacroCollector<'a, 'b> {
|
||||||
Item::Impl(_imp) => {}
|
fn visit_macro(&mut self, i: &'b Macro) {
|
||||||
Item::Trait(_) => {}
|
self.macros.push(i);
|
||||||
|
|
||||||
// Global-ish things
|
|
||||||
Item::Static(f) => collect_from_expr(&f.expr, macros),
|
|
||||||
Item::Const(f) => collect_from_expr(&f.expr, macros),
|
|
||||||
Item::Mod(s) => {
|
|
||||||
if let Some((_, block)) = &s.content {
|
|
||||||
for item in block {
|
|
||||||
collect_from_item(item, macros);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// None of these we can really do anything with at the item level
|
|
||||||
Item::Macro2(_)
|
|
||||||
| Item::Enum(_)
|
|
||||||
| Item::ExternCrate(_)
|
|
||||||
| Item::ForeignMod(_)
|
|
||||||
| Item::TraitAlias(_)
|
|
||||||
| Item::Type(_)
|
|
||||||
| Item::Struct(_)
|
|
||||||
| Item::Union(_)
|
|
||||||
| Item::Use(_)
|
|
||||||
| Item::Verbatim(_) => {}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn collect_from_block<'a>(block: &'a Block, macros: &mut Vec<CollectedMacro<'a>>) {
|
|
||||||
for stmt in &block.stmts {
|
|
||||||
match stmt {
|
|
||||||
Stmt::Item(item) => collect_from_item(item, macros),
|
|
||||||
Stmt::Local(local) => {
|
|
||||||
if let Some((_eq, init)) = &local.init {
|
|
||||||
collect_from_expr(init, macros);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Stmt::Expr(exp) | Stmt::Semi(exp, _) => collect_from_expr(exp, macros),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn collect_from_expr<'a>(expr: &'a Expr, macros: &mut Vec<CollectedMacro<'a>>) {
|
|
||||||
// collect an expr from the exprs, descending into blocks
|
|
||||||
match expr {
|
|
||||||
Expr::Macro(macro_) => {
|
|
||||||
if macro_.mac.path.segments[0].ident == "rsx"
|
|
||||||
|| macro_.mac.path.segments[0].ident == "render"
|
|
||||||
{
|
|
||||||
macros.push(¯o_.mac);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::MethodCall(e) => {
|
|
||||||
collect_from_expr(&e.receiver, macros);
|
|
||||||
for expr in e.args.iter() {
|
|
||||||
collect_from_expr(expr, macros);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Expr::Assign(exp) => {
|
|
||||||
collect_from_expr(&exp.left, macros);
|
|
||||||
collect_from_expr(&exp.right, macros);
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::Async(b) => collect_from_block(&b.block, macros),
|
|
||||||
Expr::Block(b) => collect_from_block(&b.block, macros),
|
|
||||||
Expr::Closure(c) => collect_from_expr(&c.body, macros),
|
|
||||||
Expr::Let(l) => collect_from_expr(&l.expr, macros),
|
|
||||||
Expr::Unsafe(u) => collect_from_block(&u.block, macros),
|
|
||||||
Expr::Loop(l) => collect_from_block(&l.body, macros),
|
|
||||||
|
|
||||||
Expr::Call(c) => {
|
|
||||||
collect_from_expr(&c.func, macros);
|
|
||||||
for expr in c.args.iter() {
|
|
||||||
collect_from_expr(expr, macros);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::ForLoop(b) => {
|
|
||||||
collect_from_expr(&b.expr, macros);
|
|
||||||
collect_from_block(&b.body, macros);
|
|
||||||
}
|
|
||||||
Expr::If(f) => {
|
|
||||||
collect_from_expr(&f.cond, macros);
|
|
||||||
collect_from_block(&f.then_branch, macros);
|
|
||||||
if let Some((_, else_branch)) = &f.else_branch {
|
|
||||||
collect_from_expr(else_branch, macros);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Expr::Yield(y) => {
|
|
||||||
if let Some(expr) = &y.expr {
|
|
||||||
collect_from_expr(expr, macros);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::Return(r) => {
|
|
||||||
if let Some(expr) = &r.expr {
|
|
||||||
collect_from_expr(expr, macros);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::Match(l) => {
|
|
||||||
collect_from_expr(&l.expr, macros);
|
|
||||||
for arm in l.arms.iter() {
|
|
||||||
if let Some((_, expr)) = &arm.guard {
|
|
||||||
collect_from_expr(expr, macros);
|
|
||||||
}
|
|
||||||
|
|
||||||
collect_from_expr(&arm.body, macros);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::While(w) => {
|
|
||||||
collect_from_expr(&w.cond, macros);
|
|
||||||
collect_from_block(&w.body, macros);
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't both formatting these for now
|
|
||||||
Expr::Array(_)
|
|
||||||
| Expr::AssignOp(_)
|
|
||||||
| Expr::Await(_)
|
|
||||||
| Expr::Binary(_)
|
|
||||||
| Expr::Box(_)
|
|
||||||
| Expr::Break(_)
|
|
||||||
| Expr::Cast(_)
|
|
||||||
| Expr::Continue(_)
|
|
||||||
| Expr::Field(_)
|
|
||||||
| Expr::Group(_)
|
|
||||||
| Expr::Index(_)
|
|
||||||
| Expr::Lit(_)
|
|
||||||
| Expr::Paren(_)
|
|
||||||
| Expr::Path(_)
|
|
||||||
| Expr::Range(_)
|
|
||||||
| Expr::Reference(_)
|
|
||||||
| Expr::Repeat(_)
|
|
||||||
| Expr::Struct(_)
|
|
||||||
| Expr::Try(_)
|
|
||||||
| Expr::TryBlock(_)
|
|
||||||
| Expr::Tuple(_)
|
|
||||||
| Expr::Type(_)
|
|
||||||
| Expr::Unary(_)
|
|
||||||
| Expr::Verbatim(_) => {}
|
|
||||||
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn byte_offset(input: &str, location: LineColumn) -> usize {
|
pub fn byte_offset(input: &str, location: LineColumn) -> usize {
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue