diff --git a/docs-src/0.5/zh/CLI/configure.md b/docs-src/0.5/zh/CLI/configure.md new file mode 100644 index 000000000..c4618e17a --- /dev/null +++ b/docs-src/0.5/zh/CLI/configure.md @@ -0,0 +1,186 @@ +# 配置项目 + +本章将教你如何使用 `Dioxus.toml` 文件配置 CLI。这里有一个 [示例](#config-example),其中有注释来描述每个键。你可以复制它,或者查看本文档以获取更完整的学习体验。 + +"🔒" 表示必填项。一些标题是必填的,但它们内部的键不是。在这种情况下,你只需要包含标题,但不需要包含键。这可能看起来有点奇怪,但是很正常。 + +## 结构 + +每个标题下面直接是其 TOML 表单。 + +### 应用程序 🔒 + +```toml +[application] +``` + +应用程序范围的配置。适用于 Web 和桌面应用。 + +* **name** 🔒 - 项目名称和标题。 + ```toml + name = "my_project" + ``` +* **default_platform** 🔒 - 此项目的目标平台 + ```toml + # 当前支持的平台:web,desktop + default_platform = "web" + ``` +* **out_dir** - 将 `dx build` 或 `dx serve` 的构建结果输出的目录。这也是 `assets` 目录将被复制到的位置。 + ```toml + out_dir = "dist" + ``` +* **asset_dir** - 包含静态资产的目录。CLI 将在构建/服务之后自动将这些资产复制到 **out_dir** 中。 + ```toml + asset_dir = "public" + ``` +- **sub_package** - 默认情况下要构建的工作区中的子包。 + ```toml + sub_package = "my-crate" + ``` + +### Web.App 🔒 + +```toml +[web.app] +``` + +Web 特定的配置。 + +* **title** - 网页标题。 + ```toml + # HTML 标题标签内容 + title = "project_name" + ``` +* **base_path** - 为应用程序构建的基本路径。当在域名的子目录下服务应用程序时,这可能很有用。例如,在构建要在 GitHub Pages 上提供服务的站点时。 + ```toml + # 应用程序将在 domain.com/my_application/ 处提供服务,因此我们需要将 base_path 修改为应用程序将被服务的路径 + base_path = "my_application" + ``` + +### Web.Watcher 🔒 + +```toml +[web.watcher] +``` + +开发服务器配置。 + +* **reload_html** - 如果为 true,则每次重新构建应用程序时 CLI 将重新构建 index.html 文件 + ```toml + reload_html = true + ``` +* **watch_path** - 要监视更改的文件和目录 + ```toml + watch_path = ["src", "public"] + ``` + +* **index_on_404** - 如果启用,当找不到路由时 Dioxus 将提供根页面。 + *当服务使用路由器的应用程序时需要此选项*。但是,当使用非 Dioxus 的东西(例如 GitHub Pages)提供应用程序时,你需要检查如何在该平台上配置它。在 GitHub Pages 上,你可以将名为 `404.html` 的 `index.html` 的副本放在同一个目录中。 + ```toml + index_on_404 = true + ``` + +### Web.Resource 🔒 + +```toml +[web.resource] +``` + +静态资源配置。 + +* **style** - 要包含在应用程序中的 CSS 文件。 + ```toml + style = [ + # 从 public_dir 包含。 + "./assets/style.css", + # 或来自在线 CDN 的资产。 + "https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css" + ] + ``` + +* **script** - 要包含在应用程序中的 JavaScript 文件。 + ```toml + script = [ + # 从 asset_dir 包含。 + "./public/index.js", + # 或来自在线 CDN。 + "https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js" + ] + ``` + +### Web.Resource.Dev 🔒 + +```toml +[web.resource.dev] +``` + +这与 [`[web.resource]`](#webresource-) 相同,但仅在开发服务器中有效。例如,如果你想在 `dx serve` 服务器中包含一个文件,但不在 `dx serve --release` 服务器中包含,那么将它放在这里。 + +### Web.Proxy + +```toml +[[web.proxy]] +``` + +与开发过程中应用程序需要的任何代理相关的配置。代理将请求转发到一个新的服务。 + +* **backend** - 要代理的服务器的 URL。CLI 将把任何位于后端相对路径下的请求转发到后端,而不是返回 404 + ```toml + backend = "http://localhost:8000/api/" + ``` + 这将导致任何发送到具有前缀 /api/ 的开发服务器的请求被重定向到本地主机的后端服务器 http://localhost:8000。路径和查询参数将原样传递(目前不支持路径重写)。 + +## 配置示例 + +这包括所有字段,无论是否必需。 + +```toml +[application] + +# 应用程序名称 +name = "project_name" + +# 默认的 Dioxus 平台 +default_platform = "web" + +# `build` & `serve` 输出路径 +out_dir = "dist" + +# 静态资源路径 +asset_dir = "public" + +[web.app] + +# HTML 标题标签内容 +title = "project_name" + +[web.watcher] + +# 当监视器触发时,重新生成 `index.html` +reload_html = true + +# 将监视的文件或目录 +watch_path = ["src", "public"] + +# 包含样式或脚本资产 +[web.resource] + +# CSS 样式文件 +style = [] + +# JavaScript 代码文件 +script = [] + +[web.resource.dev] + +# 与 [web.resource] 相同,但用于开发服务器 + +# CSS 样式文件 +style = [] + +# JavaScript 文件 +script = [] + +[[web.proxy]] +backend = "http://localhost:8000/api/" +``` diff --git a/docs-src/0.5/zh/CLI/creating.md b/docs-src/0.5/zh/CLI/creating.md new file mode 100644 index 000000000..141e0bb8a --- /dev/null +++ b/docs-src/0.5/zh/CLI/creating.md @@ -0,0 +1,24 @@ +# 创建项目 + +安装了 Dioxus CLI 之后,你可以使用它来创建自己的项目! + +## 初始化项目 + +首先,运行 `dx new` 命令来创建一个新项目。 + +> 它会克隆这个 [模板](https://github.com/DioxusLabs/dioxus-template),该模板用于创建 dioxus 应用程序。 +> +> 你可以通过传递 `template` 参数从不同的模板创建项目: +> ``` +> dx new --template gh:dioxuslabs/dioxus-template +> ``` + +接下来,使用 `cd project-name` 导航到你的新项目中,或者直接在 IDE 中打开它。 + +> 在运行项目之前,请确保已安装 WASM 目标。 +> 你可以使用 rustup 安装 rust 的 WASM 目标: +> ``` +> rustup target add wasm32-unknown-unknown +> ``` + +最后,使用 `dx serve` 来服务你的项目!CLI 将告诉你它正在哪个地址上提供服务,以及其他信息,如代码警告。 \ No newline at end of file diff --git a/docs-src/0.5/zh/CLI/index.md b/docs-src/0.5/zh/CLI/index.md new file mode 100644 index 000000000..4e40774f9 --- /dev/null +++ b/docs-src/0.5/zh/CLI/index.md @@ -0,0 +1,13 @@ +# 介绍 + +✨ **Dioxus CLI** ✨ 是一个帮助你快速启动 Dioxus 项目的工具。 + +这里没有命令的文档,但是一旦你安装了 CLI,你可以使用 `dx --help` 查看所有命令!此外,你可以运行 `dx --help` 来获取特定命令的帮助。 + +## 特性 + +* 构建和打包 Dioxus 项目。 +* 格式化 `rsx` 代码。 +* 热加载。 +* 从模板仓库创建一个 Dioxus 项目。 +* 等等! diff --git a/docs-src/0.5/zh/CLI/translate.md b/docs-src/0.5/zh/CLI/translate.md new file mode 100644 index 000000000..0c3147ae8 --- /dev/null +++ b/docs-src/0.5/zh/CLI/translate.md @@ -0,0 +1,105 @@ +# 翻译现有的 HTML + +Dioxus 使用一种称为 RSX 的自定义格式来表示 HTML,因为它更简洁,看起来更像 Rust 代码。然而,将现有的 HTML 转换为 RSX 可能有点麻烦。这就是为什么 Dioxus 提供了一个名为 `dx translate` 的工具,它可以自动将 HTML 转换为 RSX! + +Dx translate 可以使将大块的 HTML 转换为 RSX 变得更加容易!让我们尝试翻译一些来自 Dioxus 主页的 HTML: + +```sh +dx translate --raw "
Fullstack, crossplatform,lightning fast, fully typed.

Dioxus is a Rust library for building apps that run on desktop, web, mobile, and more.

Trusted by top companies

" +``` + +我们得到以下的 RSX,你可以轻松地复制并粘贴到你的代码中: + +```rs +div { class: "relative w-full mx-4 sm:mx-auto text-gray-600", +div { class: "text-[3em] md:text-[5em] font-semibold dark:text-white text-ghdarkmetal font-sans py-12 flex flex-col", +span { "Fullstack, crossplatform," } +span { "lightning fast, fully typed." } +} +h3 { class: "text-[2em] dark:text-white font-extralight text-ghdarkmetal pt-4 max-w-screen-md mx-auto", +"Dioxus is a Rust library for building apps that run on desktop, web, mobile, and more." +} +div { class: "pt-12 text-white text-[1.2em] font-sans font-bold flex flex-row justify-center space-x-4", +a { +href: "/learn/0.5/getting_started", +data_dioxus_id: "216", +dioxus_prevent_default: "onclick", +class: "bg-red-600 py-2 px-8 hover:-translate-y-2 transition-transform duration-300", +"Quickstart" +} +a { +dioxus_prevent_default: "onclick", +href: "/learn/0.5/reference", +data_dioxus_id: "214", +class: "bg-blue-500 py-2 px-8 hover:-translate-y-2 transition-transform duration-300", +"Read the docs" +} +} +div { class: "max-w-screen-2xl mx-auto pt-36", +h1 { class: "text-md", "Trusted by top companies" } +div { class: "pt-4 flex flex-row flex-wrap justify-center", +div { class: "h-12 w-40 p-2 m-4 flex justify-center items-center", +img { src: "static/futurewei_bw.png" } +} +div { class: "h-12 w-40 p-2 m-4 flex justify-center items-center", +img { src: "static/airbuslogo.svg" } +} +div { class: "h-12 w-40 p-2 m-4 flex justify-center items-center", +img { src: "static/ESA_logo.svg" } +} +div { class: "h-12 w-40 p-2 m-4 flex justify-center items-center", +img { src: "static/yclogo.svg" } +} +div { class: "h-12 w-40 p-2 m-4 flex justify-center items-center", +img { src: "static/satellite.webp" } +} +} +} +} +``` + +## 使用方法 + +`dx translate` 命令有几个标志,你可以使用它们来控制你的 HTML 输入和 RSX 输出。 + +你可以使用 `--file` 标志将 HTML 文件翻译为 RSX: + +```sh +dx translate --file index.html +``` + +或者你可以使用 `--raw` 标志将一串 HTML 转换为 RSX: + +```sh +dx translate --raw "
Hello world
" +``` + +这两个命令将输出以下 RSX: + +```rs +div { "Hello world" } +``` + +`dx translate` 命令将 RSX 输出到 stdout。你可以使用 `--output` 标志将 RSX 写入文件。 + +```sh +dx translate --raw "
Hello world
" --output index.rs +``` + +你可以使用 `--component` 标志自动创建一个组件。 + +```sh +dx translate --raw "
Hello world
" --component +``` + +这将输出以下组件: + +```rs +fn component() -> Element { +rsx! { +div { "Hello world" } +} +} +``` + +要了解更多 `dx translate` 支持的不同标志,请运行 `dx translate --help`。 diff --git a/docs-src/0.5/zh/contributing/governance.md b/docs-src/0.5/zh/contributing/governance.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/docs-src/0.5/zh/contributing/governance.md @@ -0,0 +1 @@ + diff --git a/docs-src/0.5/zh/contributing/guiding_principles.md b/docs-src/0.5/zh/contributing/guiding_principles.md new file mode 100644 index 000000000..aa7ebfd71 --- /dev/null +++ b/docs-src/0.5/zh/contributing/guiding_principles.md @@ -0,0 +1,37 @@ +# 总体目标 + +本文档概述了 Dioxus 的一些总体目标。这些目标并不是一成不变的,但它们代表了项目的一般指导方针。 + +Dioxus 的目标是让构建**可扩展的跨平台应用程序**变得容易。 + +## 跨平台 + +Dioxus 默认设计为跨平台。这意味着应该可以轻松构建在 Web、桌面和移动设备上运行的应用程序。但是,Dioxus 也应该足够灵活,允许用户在需要时选择平台特定的功能。`use_eval` 就是其中的一个例子。默认情况下,Dioxus 不假设平台支持 JavaScript,但它提供了一个钩子,允许用户在需要时选择 JavaScript。 + +## 性能 + +随着 Dioxus 应用程序的增长,它们应该保持相对高效,而无需进行手动优化。会有需要手动优化的情况,但是 Dioxus 应该尽量使这些情况变得尽可能少见。 + +Dioxus 核心架构的一个好处是,即使组件经常重新渲染,它也能提供合理的性能。它基于虚拟 DOM,执行差异比较,即使组件树的大部分内容被重新运行,也应该防止不必要的重新渲染。此外,Dioxus 将 RSX 树的静态部分分组在一起,以完全跳过差异比较。 + +## 类型安全 + +随着团队的壮大,Rust 的类型安全性是一个巨大的优势。Dioxus 应该利用这个优势,使得使用大型团队构建应用程序变得容易。 + +为了充分利用 Rust 的类型系统,Dioxus 应尽可能避免公开 `Any` 类型和字符串类型的 API。 + +## 开发者体验 + +Dioxus 应该易于学习且使用起来舒适。 + +- Dioxus 的 API 在可能的情况下尽量保持与 React API 的接近。这使得如果他们已经了解 React,学习 Dioxus 就更容易。 + +- 我们可以通过提供多层次的 API 来避免简单和灵活性之间的权衡:一个是用于非常常见的用例,一个是用于低级控制 + + - Hooks:hooks crate 包含了最常见的用例,但 `use_hook` 提供了一种访问底层持久值的方式,如果需要的话。 + - 平台配置中的建造者模式:建造者模式用于默认使用最常见的用例,但用户可以根据需要更改默认值。 + +- 文档: + - 所有公共 API 应该有 Rust 文档 + - 所有公共功能都应提供示例。这些示例既作为文档又作为测试。CI 会检查它们是否继续编译 + - 最常见的工作流程应该在指南中进行说明 diff --git a/docs-src/0.5/zh/contributing/index.md b/docs-src/0.5/zh/contributing/index.md new file mode 100644 index 000000000..e68620988 --- /dev/null +++ b/docs-src/0.5/zh/contributing/index.md @@ -0,0 +1,110 @@ +# 贡献 + +开发工作在 [Dioxus GitHub 仓库](https://github.com/DioxusLabs/dioxus)中进行。如果您发现了 bug 或者有新功能的想法,请提交一个 issue(但请先检查是否已经有人提交过了)。 + +[GitHub 讨论](https://github.com/DioxusLabs/dioxus/discussions)可以用作寻求帮助或讨论功能的地方。您也可以加入 [我们的 Discord 频道](https://discord.gg/XgGxMSkvUM),在那里有一些开发讨论。 + +## 改进文档 + +如果您想改进文档,欢迎提供 PR!Rust 文档([源代码](https://github.com/DioxusLabs/dioxus/tree/main/packages))和这个指南([源代码](https://github.com/DioxusLabs/docsite/tree/main/docs-src/0.5/en))可以在各自的 GitHub 仓库中找到。 + +## 生态系统工作 + +让 React 变得伟大的部分是丰富的生态系统。我们也希望 Dioxus 有同样的生态系统!因此,如果您有一个想要编写的库,并且许多人会从中受益,那么将会受到欢迎。您可以在 [npm.js](https://www.npmjs.com/search?q=keywords:react-component) 上查找灵感。完成后,请将您的库添加到 [awesome dioxus](https://github.com/DioxusLabs/awesome-dioxus) 列表中,或在 [Discord](https://discord.gg/XgGxMSkvUM) 的 `#I-made-a-thing` 频道中分享。 + +## Bug & Features + +如果您修复了 [一个开放的问题](https://github.com/DioxusLabs/dioxus/issues),请随时提交 PR!您也可以查看 [路线图](./roadmap.md) 并开始处理其中的某个任务。考虑先 [联系团队](https://discord.gg/XgGxMSkvUM) ,以确保所有人都在同一页面上,并且您不会做无用的工作! + +所有的 Pull Request(包括团队成员提交的)都必须由至少一名其他团队成员批准。 +关于设计、架构、重大更改、权衡等更大、更微妙的决策是通过团队共识来做出的。 + +## 贡献前 + +您可能会惊讶地发现,在首次提交 PR 时,许多检查都会失败。 +这就是为什么在贡献之前您应该首先运行这些命令,它将为您节省 *大量* 的时间,因为 GitHub CI 执行所有这些命令要比您的 PC 慢得多。 + +- 使用 [rustfmt](https://github.com/rust-lang/rustfmt) 格式化代码: + +```sh +cargo fmt -- src/**/**.rs +``` + +- 您可能需要在 Linux(Ubuntu/deb)上安装一些软件包,然后以下命令才能成功完成(存储库根目录中还有一个 Nix flake): + +```sh +sudo apt install libgdk3.0-cil libatk1.0-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libsoup-3.0-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev +``` + +- 检查所有代码 [cargo check](https://doc.rust-lang.org/cargo/commands/cargo-check.html): + +```sh +cargo check --workspace --examples --tests +``` + +- 检查 [Clippy](https://doc.rust-lang.org/clippy/) 是否生成任何警告。请修复这些警告! + +```sh +cargo clippy --workspace --examples --tests -- -D warnings +``` + +- 使用 [cargo-test](https://doc.rust-lang.org/cargo/commands/cargo-test.html) 测试所有代码: + +```sh +cargo test --all --tests +``` + +- 更多测试,这次使用 [cargo-make](https://sagiegurari.github.io/cargo-make/)。以下是所有步骤,包括安装: + +```sh +cargo install --force cargo-make +cargo make tests +``` + +- 使用 [MIRI](https://github.com/rust-lang/miri) 测试不安全的 crate。目前,这用于 `dioxus-core` 和 `dioxus-native-core` 中的两个 MIRI 测试: + +```sh +cargo miri test --package dioxus-core --test miri_stress +cargo miri test --package dioxus-native-core --test miri_native +``` + +- 使用 Playwright 进行测试。这在浏览器中直接测试 UI。以下是所有步骤,包括安装: + **免责声明:这可能会在您的计算机上无缘无故失败,而不是您的错。** 无论如何,请提交 PR! + +```sh +cd playwright-tests +npm ci +npm install -D @playwright/test +npx playwright install --with-deps +npx playwright test +``` + +## 如何使用本地 crate 测试 dioxus +如果您正在开发一个功能,您应该在提交 PR 之前在您的本地设置中测试它。这个过程可以确保您了解您的代码功能,然后由同行审查。 + +- Fork 下面的 github 存储库 (DioxusLabs/dioxus): + +`https://github.com/DioxusLabs/dioxus` + +- 创建一个新的或者使用一个已经存在的 rust crate (如果您将使用一个已经存在的 rust crate,请忽略此步骤): + 这是我们将测试 forked 中的功能的地方 + +```sh +cargo new --bin demo +``` + +- 在 Cargo.toml 中为您的 rust crate(新的/现有的)添加 dioxus 依赖项: + +```toml +dioxus = { path = "/dioxus/packages/dioxus", features = ["web", "router"] } +``` + +上面的示例是为了 dioxus-web,使用 dioxus-router。要了解不同渲染器的依赖关系,请访问[此处](https://dioxuslabs.com/learn/0.5/getting_started)。 + +- 运行和测试您的功能 + +```sh +dx serve +``` + +如果这是您第一次使用 dioxus,请阅读 [指南](https://dioxuslabs.com/learn/0.5/guide) 以熟悉 dioxus。 diff --git a/docs-src/0.5/zh/contributing/project_structure.md b/docs-src/0.5/zh/contributing/project_structure.md new file mode 100644 index 000000000..f652cbbfc --- /dev/null +++ b/docs-src/0.5/zh/contributing/project_structure.md @@ -0,0 +1,50 @@ +# 项目结构 + +Dioxus 组织中有许多包。本文档将帮助您了解每个包的目的以及它们如何彼此关联。 + +## 渲染器 + +- [Desktop](https://github.com/DioxusLabs/dioxus/tree/main/packages/desktop): 一个渲染器,可以在本地运行 Dioxus 应用程序,但是使用系统 webview 进行渲染 +- [Mobile](https://github.com/DioxusLabs/dioxus/tree/main/packages/mobile): 一个渲染器,可以在本地运行 Dioxus 应用程序,但是使用系统 webview 进行渲染。目前这是 desktop 渲染器的一个副本 +- [Web](https://github.com/DioxusLabs/dioxus/tree/main/packages/Web): 将 Dioxus 应用程序编译为 WASM 并操作 DOM 在浏览器中渲染 +- [Liveview](https://github.com/DioxusLabs/dioxus/tree/main/packages/liveview): 在服务器上运行,并使用浏览器中的 websocket 代理进行渲染的渲染器 +- [Plasmo](https://github.com/DioxusLabs/blitz/tree/master/packages/plasmo): 将 HTML 样式树渲染到终端的渲染器 +- [TUI](https://github.com/DioxusLabs/blitz/tree/master/packages/dioxus-tui): 使用 Plasmo 在终端中渲染 Dioxus 应用程序的渲染器 +- [Blitz-Core](https://github.com/DioxusLabs/blitz/tree/master/packages/blitz-core): 使用 WGPU 渲染 HTML 样式树的实验性本地渲染器 +- [Blitz](https://github.com/DioxusLabs/blitz): 使用 Blitz-Core 使用 WGPU 渲染 Dioxus 应用程序的实验性本地渲染器 +- [SSR](https://github.com/DioxusLabs/dioxus/tree/main/packages/ssr): 在服务器上运行 Dioxus 应用程序,并将其呈现为 HTML 的渲染器 + +## 状态管理/Hooks + +- [Hooks](https://github.com/DioxusLabs/dioxus/tree/main/packages/hooks): Dioxus 应用程序的常见 Hooks 集合 +- [Signals](https://github.com/DioxusLabs/dioxus/tree/main/packages/signals): Dioxus 应用程序的实验性状态管理库。目前包含一个 `Copy` 版本的 Signal +- [SDK](https://github.com/DioxusLabs/sdk): 与系统接口交互的平台无关 Hooks 集合(剪贴板、相机等)。 +- [Fermi](https://github.com/DioxusLabs/dioxus/tree/main/packages/fermi): Dioxus 应用程序的全局状态管理库。 +- [Router](https://github.com/DioxusLabs/dioxus/tree/main/packages/router): Dioxus 应用程序的客户端路由器 + +## 核心工具 + +- [core](https://github.com/DioxusLabs/dioxus/tree/main/packages/core): 每个 Dioxus 应用程序都使用的核心虚拟 DOM 实现 + - 您可以在 [此博客文章](https://dioxuslabs.com/blog/templates-diffing/) 和 [指南中的自定义渲染器部分](../custom_renderer/index.md) 中了解更多关于 core 的架构信息 +- [RSX](https://github.com/DioxusLabs/dioxus/tree/main/packages/RSX): 用于热重载、自动格式化和宏的 RSX 的核心解析 +- [core-macro](https://github.com/DioxusLabs/dioxus/tree/main/packages/core-macro): 用于编写 Dioxus 应用程序的 rsx! 宏。(这是 RSX crate 的包装器) +- [HTML 宏](https://github.com/DioxusLabs/dioxus-html-macro): RSX 宏的 HTML 样式替代方案 + +## 本地渲染器工具 + +- [native-core](https://github.com/DioxusLabs/blitz/tree/main/packages/native-core): 增量计算状态(主要是样式)的树 + - 您可以在 [指南的自定义渲染器部分](../custom_renderer/index.html#native-core) 中了解有关 native-core 如何帮助您构建本地渲染器的更多信息 +- [native-core-macro](https://github.com/DioxusLabs/blitz/tree/main/packages/native-core-macro): 本地 core 的辅助宏 +- [Taffy](https://github.com/DioxusLabs/taffy): 驱动 Blitz-Core、Plasmo 和 Bevy UI 的布局引擎 + +## Web 渲染器工具 + +- [HTML](https://github.com/DioxusLabs/dioxus/tree/main/packages/html): 定义了特定于 HTML 的元素、事件和属性 +- [Interpreter](https://github.com/DioxusLabs/dioxus/tree/main/packages/interpreter): 定义了 Web 和 Desktop 渲染器使用的浏览器绑定 + +## 开发者工具 + +- [hot-reload](https://github.com/DioxusLabs/dioxus/tree/main/packages/hot-reload): 使用 RSX crate 热重载任何 rsx! 宏的静态部分的宏。该宏与具有[集成](https://crates.io/crates/dioxus-hot-reload)的任何非 Web 渲染器一起使用 +- [autofmt](https://github.com/DioxusLabs/dioxus/tree/main/packages/autofmt): 格式化 RSX 代码 +- [rsx-rosetta](https://github.com/DioxusLabs/dioxus/tree/main/packages/RSX-rosetta): 处理 HTML 和 RSX 之间的转换 +- [CLI](https://github.com/DioxusLabs/dioxus/tree/main/packages/cli): 用于辅助 Dioxus 使用的命令行界面和 VSCode 扩展 diff --git a/docs-src/0.5/zh/contributing/roadmap.md b/docs-src/0.5/zh/contributing/roadmap.md new file mode 100644 index 000000000..903097eb7 --- /dev/null +++ b/docs-src/0.5/zh/contributing/roadmap.md @@ -0,0 +1,138 @@ +# 路线图和功能集 + +此功能集和路线图可以帮助您确定 Dioxus 目前的功能是否适合您的需求。 + +如果您需要的功能尚不存在,或者您想要为路线图上的项目做出贡献,请随时通过[加入 Discord](https://discord.gg/XgGxMSkvUM)参与进来。 + +一般来说,以下是每个平台的状态: + +- **Web**:Dioxus 是纯 Web 应用的绝佳选择,特别是对于 CRUD/复杂应用程序。但是,它缺少 React 的生态系统,因此您可能会缺少一些组件库或一些有用的 Hook。 + +- **SSR**:Dioxus 是预渲染、水合和在 Web 端点上呈现 HTML 的绝佳选择。但要注意,虚拟 DOM 目前不是 `Send + Sync`。 + +- **Desktop**:您现在可以构建非常有竞争力的单窗口桌面应用程序。但是,多窗口应用程序需要来自 Dioxus 核心的支持,目前尚未准备好。 + +- **Mobile**:移动支持还很年轻。您将边学边摸索,而且没有多少支持外设的支持包。 + +- **LiveView**:LiveView 支持还很年轻。您将边学边摸索。值得庆幸的是,没有一项工作是太难的,任何工作都可以上游到 Dioxus。 + +## 功能 + +--- + +| 功能 | 状态 | 描述 | +|---------------------------|--------|---------------------------------------------------------------| +| 条件渲染 | x | if/then 隐藏/显示组件 | +| Map, Iterator | x | map/filter/reduce 生成 rsx! | +| Keyed 组件 | x | 使用键进行高级差异 | +| Web | x | 用于 Web 浏览器的渲染器 | +| Desktop (webview) | x | 用于桌面的渲染器 | +| 共享状态 (Context) | x | 通过树共享状态 | +| Hooks | x | 组件中的内存单元 | +| SSR | x | 直接呈现为字符串 | +| 组件子节点 | x | cx.children() 作为节点列表 | +| 无头组件 | x | 不返回实际元素的组件 | +| 片段 | x | 没有真实根的多个元素 | +| 手动 Props | x | 使用展开语法手动传递 props | +| 受控输入 | x | 围绕输入的有状态包装器 | +| CSS/Inline 样式 | x | 内联样式/属性组的语法 | +| 自定义元素 | x | 定义新的元素原语 | +| Suspense | x | 从未来/承诺安排未来呈现 | +| 集成错误处理 | x | 使用?语法优雅地处理错误 | +| NodeRef | x | 直接访问节点 | +| 重新水合 | x | 预先呈现为 HTML 以加快首次内容呈现速度 | +| 无卡顿渲染 | x | 大型差异在帧之间分段,实现丝般顺滑的过渡 | +| Effects | x | 在组件提交到呈现后运行效果 | +| 门户 | * | 在传统树结构之外呈现节点 | +| 合作调度 | * | 将重要事件优先于不重要事件 | +| 服务器组件 | * | 用于 SPA 和服务器的混合组件 | +| 捆绑拆分 | i | 高效异步加载应用程序 | +| 懒加载组件 | i | 随着页面加载动态加载新组件 | +| 一流全局状态 | x | redux/recoil/mobx 在 context 之上 | +| 原生运行 | x | 作为便携式二进制运行,无需运行时 (Node) | +| 子树记忆 | x | 跳过静态元素子树的差异 | +| 高效模板 | x | rsx! 调用在 DOM 端被转换为模板 | +| 编译时正确性 | x | 在无效模板布局时抛出错误 | +| 启发式引擎 | x | 跟踪组件内存使用情况以最小化未来分配 | +| 细粒度反应 | i | 对于细粒度更新跳过差异检查 | + +- x = 实现并且可用 +- \* = 正在积极开发中 +- i = 尚未实现或正在开发中 + +## 路线图 + +以下功能计划在未来的 Dioxus 中实现: + +### 核心 + +- [x] 发布 Dioxus 核心 +- [x] 更新文档以包含更多理论知识,并更加全面 +- [x] 支持 HTML-side 模板以实现极快的 DOM 操作 +- [ ] 支持相同虚拟 DOM 的多个渲染器(子树) +- [ ] 支持 ThreadSafe (Send + Sync) +- [ ] 支持 Portals + +### SSR + +- [x] SSR 支持 + 水合 +- [x] 集成 suspense 对 SSR 的支持 + +### 桌面 + +- [ ] 声明式窗口管理 +- [ ] 用于构建/打包的模板 +- [ ] 本地访问 Canvas/WebGL 上下文 + +### 移动 + +- [ ] 移动标准库 + - [ ] GPS + - [ ] 相机 + - [ ] 文件系统 + - [ ] 生物识别 + - [ ] WiFi + - [ ] 蓝牙 + - [ ] 通知 + - [ ] 剪贴板 +- [ ] 动画 + +### 捆绑 (CLI) + +- [x] 从 HTML 转换为 RSX +- [x] Dev 服务器 +- [x] 实时重新加载 +- [x] 从 JSX 转换为 RSX +- [ ] 热模块替换 +- [ ] 代码拆分 +- [x] 资源宏 +- [x] CSS 管道 +- [x] 图像管道 + +### 基本 Hook + +- [x] 路由器 +- [x] 全局状态管理 +- [ ] 调整大小观察器 + +## 进行中的工作 + +### 构建工具 + +我们目前正在开发我们自己的构建工具,称为 [Dioxus CLI](https://github.com/DioxusLabs/dioxus/tree/main/packages/cli),它将支持: + +- 交互式 TUI +- 实时重新配置 +- 热 CSS 重新加载 +- 浏览器和源代码之间的双向数据绑定 +- `rsx!` 的解释器 +- 发布到 github/netlify/vercel +- iOS/Desktop 等的打包 + +### 服务器组件支持 + +尽管目前尚未完全实现,但预期 LiveView 应用程序可以是 Wasm 和服务器呈现之间的混合,其中仅页面的部分是“实时的”,页面的其余部分要么是服务器呈现的,要么是静态生成的,或者由主机 SPA 处理。 + +### 本地渲染 + +我们目前正在开发一个使用 WGPU 的 Dioxus 本地渲染器,称为 [Blitz](https://github.com/DioxusLabs/blitz/)。这将允许您构建可以在 iOS、Android 和桌面上本地呈现的应用程序。 diff --git a/docs-src/0.5/zh/contributing/walkthrough_readme.md b/docs-src/0.5/zh/contributing/walkthrough_readme.md new file mode 100644 index 000000000..626667ab3 --- /dev/null +++ b/docs-src/0.5/zh/contributing/walkthrough_readme.md @@ -0,0 +1,135 @@ +# Hello World示例内部的操作步骤 + +本操作步骤将带您了解Hello World示例程序的内部操作。它将解释Dioxus内部的主要部分如何相互作用,以将readme示例从源文件转换为运行中的应用程序。本指南应该作为Dioxus内部操作的高级概述。它不是一份全面的指南。 + +核心crate的大致工作方式如下: + +![](https://mermaid.ink/img/pako:eNqNk01v2zAMhv8KocsuTQ876lCgWAb0sGDD0mMAg7PoWogsBvpwWhT976MlJ3OKbKtOEvmIfEWRr6plQ0qrmDDR2uJTwGE1ft55kBXIGwqNHQYyVvywWt3BA3rjKGj4gs5BX0-V_1n4QtUthW_Mh6WzWgryg537OpJPsQJ_zsX9PrmG0fBwWxM2NIH1nmdRFuxTn4C7K4mn9djTpYAjWsnTcQBaSJiWxIcULEVILCIiu5Egyf3RhpTRwfr75tOC73LKggGmQkUcBLcDVUJyFoF_qcEkoxEVzZHDvjIXpnOhtm1PJp8rvcGw37Z8oPu4FlkvhVvbrivGypyP_3dWXRo2WdrAsp-fN391Qd5n1BBnSU0-GDy9sHyGo678xcOyOU7fMHcMHINNtcgIPfP-Wr2WAu6NeeRzGTS0z7fxgEd_7T3_Zi8b5kp1T1IxvvgWfjlu9x-SexHqo1VTN2qgMKA1MoavU6CdkkaSBlJatoY6zC7t1M6_CYo58VZUKZ1CphtVo8yDq3SHLopVJiZx2NTRLhP-9htxEk8q?type=png) + +## 源文件 + +我们从一个Hello World程序开始。该程序在webview中呈现一个带有文本“Hello World”的桌面应用程序。 + +```rust, no_run +{{#include src/doc_examples/readme.rs}} +``` + +[![](https://mermaid.ink/img/pako:eNqNkT1vwyAQhv8KvSlR48HphtQtqjK0S6tuSBGBS0CxwcJHk8rxfy_YVqxKVdR3ug_u4YXrQHmNwOFQ-bMyMhB7fReOJbVxfwyyMSy0l7GSpW1ARda727ksUy5MuSyKgvBC5ULA1h5N8WK_kCkfHWHgrBuiXsBynrvdsY9E3u1iM_eyvFOVVadMnELOap-o1911JLPHZ1b-YqLTc3LjTt7WifTZMJPsPdx1ov3Z_ellfcdL8R8vmTy5eUqsTUpZ-vzZzjAEK6gx1NLqtJwuNwSQwRoF8BRqGU4ChOvTORnJf3w7BZxCxBXERkvCjZXpQTXwg6zaVEVtyYe3cdvD0vsf4bucgw)](https://mermaid.live/edit#pako:eNqNkT1vwyAQhv8KvSlR48HphtQtqjK0S6tuSBGBS0CxwcJHk8rxfy_YVqxKVdR3ug_u4YXrQHmNwOFQ-bMyMhB7fReOJbVxfwyyMSy0l7GSpW1ARda727ksUy5MuSyKgvBC5ULA1h5N8WK_kCkfHWHgrBuiXsBynrvdsY9E3u1iM_eyvFOVVadMnELOap-o1911JLPHZ1b-YqLTc3LjTt7WifTZMJPsPdx1ov3Z_ellfcdL8R8vmTy5eUqsTUpZ-vzZzjAEK6gx1NLqtJwuNwSQwRoF8BRqGU4ChOvTORnJf3w7BZxCxBXERkvCjZXpQTXwg6zaVEVtyYe3cdvD0vsf4bucgw) + +## rsx!宏 + +在Rust编译器运行程序之前,它将展开所有[宏](https://doc.rust-lang.org/reference/procedural-macros.html)。这是hello world示例展开后的样子: + +```rust, no_run +{{#include src/doc_examples/readme_expanded.rs}} +``` + +rsx宏将rsx的静态部分(模板)和动态部分([dynamic_nodes](https://docs.rs/dioxus-core/0.5.0/dioxus_core/prelude/struct.VNode.html#structfield.dynamic_nodes)和[dynamic_attributes](https://docs.rs/dioxus-core/0.5.0/dioxus_core/prelude/struct.VNode.html#structfield.dynamic_attrs))分开。 + +静态模板只包含rsx的在运行时不会改变的部分,其中包含动态部分的占位符: + +[![](https://mermaid.ink/img/pako:eNqdksFuwjAMhl8l8wkkKtFx65njdtm0E0GVSQKJoEmVOgKEeHecUrXStO0wn5Lf9u8vcm6ggjZQwf4UzspiJPH2Ib3g6NLuELG1oiMkp0TsLs9EDu2iUeSCH8tz2HJmy3lRFPrqsXGq9mxeLzcbCU6LZSUGXWRdwnY7tY7Tdoko-Dq1U64fODgiUfzJMeuOe7_ZGq-ny2jNhGQu9DqT8NUK6w72RcL8dxgdzv4PnHLAKf-Fk80HoBUDrfkqeBkTUd8EC2hMbNBpXtYtJySQNQ0PqPioMR4lSH_nOkwUPq9eQUUxmQWkViOZtUN-UwPVHk8dq0Y7CvH9uf3-E9wfrmuk1A)](https://mermaid.live/edit#pako:eNqdksFuwjAMhl8l8wkkKtFx65njdtm0E0GVSQKJoEmVOgKEeHecUrXStO0wn5Lf9u8vcm6ggjZQwf4UzspiJPH2Ib3g6NLuELG1oiMkp0TsLs9EDu2iUeSCH8tz2HJmy3lRFPrqsXGq9mxeLzcbCU6LZSUGXWRdwnY7tY7Tdoko-Dq1U64fODgiUfzJMeuOe7_ZGq-ny2jNhGQu9DqT8NUK6w72RcL8dxgdzv4PnHLAKf-Fk80HoBUDrfkqeBkTUd8EC2hMbNBpXtYtJySQNQ0PqPioMR4lSH_nOkwUPq9eQUUxmQWkViOZtUN-UwPVHk8dq0Y7CvH9uf3-E9wfrmuk1A) + +动态节点和动态属性是在运行时可以更改的rsx部分: + +[![](https://mermaid.ink/img/pako:eNp1UcFOwzAM_RXLVzZpvUbighDiABfgtkxTlnirtSaZUgc0df130hZEEcwny35-79nu0EZHqHDfxA9bmyTw9KIDlGjz7pDMqQZ3DsazhVCQ7dQbwnEiKxwDvN3NqhN4O4C3q_VaIztYKXjkQ7184HcCG3MQSgq6Mes1bjbTPAV3RdqIJN5l-V__2_Fcf5iY68dgG7ZHBT4WD5ftZfIBN7dQ_Tj4w1B9MVTXGZa_GMYdcIGekjfsymW7oaFRavKkUZXUmXTUqENfcCZLfD0Hi0pSpgXmkzNC92zKATyqvWnaUiXHEtPz9KrxY_0nzYOPmA)](https://mermaid.live/edit#pako:eNp1UcFOwzAM_RXLVzZpvUbighDiABfgtkxTlnirtSaZUgc0df130hZEEcwny35-79nu0EZHqHDfxA9bmyTw9KIDlGjz7pDMqQZ3DsazhVCQ7dQbwnEiKxwDvN3NqhN4O4C3q_VaIztYKXjkQ7184HcCG3MQSgq6Mes1bjbTPAV3RdqIJN5l-V__2_Fcf5iY68dgG7ZHBT4WD5ftZfIBN7dQ_Tj4w1B9MVTXGZa_GMYdcIGekjfsymW7oaFRavKkUZXUmXTUqENfcCZLfD0Hi0pSpgXmkzNC92zKATyqvWnaUiXHEtPz9KrxY_0nzYOPmA) + +## 启动应用 + +通过调用`launch`函数并提供根组件来启动应用程序。在内部,此函数将使用[wry](https://docs.rs/wry/latest/wry/)创建一个新的web视图,并使用readme示例中的根组件(`fn app()`)。本指南不会详细解释渲染器,但您可以在[自定义渲染器](/guide/custom-renderer)部分了解更多信息。 + +## 虚拟DOM + +在我们深入讨论虚拟DOM中的初始渲染之前,我们需要讨论一下什么是虚拟DOM。虚拟DOM是DOM的表示,用于将当前DOM与新DOM进行差异比较。然后使用此差异创建需要应用于DOM以使其与虚拟DOM同步的变异列表。 + +虚拟DOM大致如下: + +```rust, no_run +pub struct VirtualDom { + // All the templates that have been created or set during hot reloading + pub(crate) templates: FxHashMap>>, + + // A slab of all the scopes that have been created + pub(crate) scopes: ScopeSlab, + + // All scopes that have been marked as dirty + pub(crate) dirty_scopes: BTreeSet, + + // Every element is actually a dual reference - one to the template and the other to the dynamic node in that template + pub(crate) elements: Slab, + + // This receiver is used to receive messages from hooks about what scopes need to be marked as dirty + pub(crate) rx: futures_channel::mpsc::UnboundedReceiver, + + // The changes queued up to be sent to the renderer + pub(crate) mutations: Mutations<'static>, +} +``` + +> 什么是 [slab](https://docs.rs/slab/latest/slab/)? +> +> slab 就像一个具有整数键的 hashmap,如果你不关心键的值。它内部由一个密集的向量支持,这使得它比 hashmap 更高效。当你将一个值插入到 slab 中时,它会返回一个整数键,你可以用它来以后检索值。 + +> Dioxus 如何使用 slab? +> +> Dioxus 使用“同步的 slab”来在渲染器和 VDOM 之间进行通信。当在虚拟 DOM 中创建一个节点时,会将 (elementId, mutation) 对传递给渲染器来标识该节点,然后渲染器将在实际 DOM 中渲染该节点。这些 id 也被虚拟 DOM 用于引用未来的突变,比如在节点上设置属性或移除节点。当渲染器向虚拟 DOM 发送事件时,它会发送事件触发的节点的 ElementId。虚拟 DOM 使用此 id 在 slab 中找到该节点,然后运行必要的事件处理程序。 + +虚拟 DOM 是一个作用域树。当组件首次渲染时,每个组件都会创建一个新的 `Scope`,并在组件卸载时回收。 + +作用域有三个主要用途: + +1. 它们存储组件使用的 hook 的状态。 +2. 它们为上下文 API 存储状态(例如:使用 [use_context_provider](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_context_provider.html))。 +3. 它们存储渲染的当前版本和先前版本的 `VNode`,以便对其进行差异比较以生成重新渲染所需的变化集。 + +### 初始渲染 + +创建并重建根作用域: + +1. 运行根组件 +2. 根组件返回一个 `VNode` +3. 为此 `VNode` 创建并添加突变到突变列表(这可能涉及创建新的子组件) +4. 将 `VNode` 存储在根的 `Scope` 中。 + +在构建完根的 `Scope` 后,所有生成的突变都会发送到渲染器,渲染器会将其应用到 DOM 上。 + +初始渲染后,根 `Scope` 如下所示: + +[![](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) + +### 等待事件 + +只有在作用域被标记为脏时,虚拟 DOM 才会重新渲染一个 `Scope`。每个 hook 负责在状态发生变化时将作用域标记为脏。Hook 可以通过向虚拟 DOM 的通道发送消息来将作用域标记为脏。你可以在 dioxus 默认包含的 hook 的 [实现](https://github.com/DioxusLabs/dioxus/tree/main/packages/hooks) 中看到这是如何实现的。调用 `needs_update()` 也会导致 hook 将其作用域标记为脏。 + +一般有两种方式标记作用域为脏: + +1. 渲染器触发事件:此事件上的事件监听器可能会被调用,如果处理事件导致任何生成的突变,则可能会将组件标记为脏。 +2. 渲染器调用 [`wait_for_work`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.wait_for_work):这会轮询 dioxus 内部的未来队列。其中一个未来可能会将组件标记为脏。 + +一旦至少有一个作用域被标记为脏,渲染器就可以调用 [`render_immediate`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.render_immediate) 来对脏作用域进行差异化处理。 + +### 差异化处理作用域 + +当用户点击“up high”按钮时,根 `Scope` 将由 `use_signal` hook 标记为脏。桌面渲染器将调用 `render_immediate`,这将对根 `Scope` 进行差异化处理。 + +要开始差异化处理过程,会运行组件函数。在运行根组件后,根 `Scope` 将如下所示: + +[![](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) + +接下来,虚拟 DOM 将比较新的 VNode 与以前的 VNode,并仅更新树中发生更改的部分。由于这种方法,在重新渲染组件时,只会将树中发生更改的部分通过渲染器更新到 DOM 中。 + +差异化算法会遍历动态属性和节点的列表,并将它们与以前的 VNode 进行比较。如果属性或节点发生了更改,将会向突变列表中添加描述更改的突变。 + +这是根 `Scope` 的差异化算法的示例(红线表示生成了突变,绿线表示没有生成突变) + +[![](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) + +## 结论 + +这仅是虚拟 DOM 工作原理的简要概述。本指南尚未涵盖的几个方面包括: + +* 虚拟 DOM 如何处理异步组件 +* 键控差异化 +* 使用 [bump allocation](https://github.com/fitzgen/bumpalo) 来高效分配 VNode。 + +如果您需要更多关于虚拟 DOM 的信息,可以阅读 [core](https://github.com/DioxusLabs/dioxus/tree/main/packages/core) crate 的代码,或在 [Discord](https://discord.gg/XgGxMSkvUM) 上联系我们。 \ No newline at end of file diff --git a/docs-src/0.5/zh/cookbook/antipatterns.md b/docs-src/0.5/zh/cookbook/antipatterns.md new file mode 100644 index 000000000..044fc183e --- /dev/null +++ b/docs-src/0.5/zh/cookbook/antipatterns.md @@ -0,0 +1,33 @@ +# Antipatterns + +This example shows what not to do and provides a reason why a given pattern is considered an "AntiPattern". Most anti-patterns are considered wrong for performance or code re-usability reasons. + +## Unnecessarily Nested Fragments + +Fragments don't mount a physical element to the DOM immediately, so Dioxus must recurse into its children to find a physical DOM node. This process is called "normalization". This means that deeply nested fragments make Dioxus perform unnecessary work. Prefer one or two levels of fragments / nested components until presenting a true DOM element. + +Only Component and Fragment nodes are susceptible to this issue. Dioxus mitigates this with components by providing an API for registering shared state without the Context Provider pattern. + +\```rust +{{#include src/doc_examples/anti_patterns.rs:nested_fragments}} +\``` + +## Incorrect Iterator Keys + +As described in the [dynamic rendering chapter](../reference/dynamic_rendering#the), list items must have unique keys that are associated with the same items across renders. This helps Dioxus associate state with the contained components and ensures good diffing performance. Do not omit keys, unless you know that the list will never change. + +\```rust +{{#include src/doc_examples/anti_patterns.rs:iter_keys}} +\``` + +## Avoid Interior Mutability in Props + +While it is technically acceptable to have a `Mutex` or a `RwLock` in the props, they will be difficult to use. + +Suppose you have a struct `User` containing the field `username: String`. If you pass a `Mutex` prop to a `UserComponent` component, that component may wish to write to the `username` field. However, when it does, the parent component will not be aware of the change, and the component will not re-render which causes the UI to be out of sync with the state. Instead, consider passing down a reactive value like a `Signal` or immutable data. + +## Avoid Updating State During Render + +Every time you update the state, Dioxus needs to re-render the component – this is inefficient! Consider refactoring your code to avoid this. + +Also, if you unconditionally update the state during render, it will be re-rendered in an infinite loop. \ No newline at end of file diff --git a/docs-src/0.5/zh/cookbook/custom_renderer.md b/docs-src/0.5/zh/cookbook/custom_renderer.md new file mode 100644 index 000000000..d96b905fa --- /dev/null +++ b/docs-src/0.5/zh/cookbook/custom_renderer.md @@ -0,0 +1,413 @@ +# Custom Renderer + +Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer. + +Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing `Mutations` and sending `UserEvents`. + +## The specifics: + +Implementing the renderer is fairly straightforward. The renderer needs to: + +1. Handle the stream of edits generated by updates to the virtual DOM +2. Register listeners and pass events into the virtual DOM's event system + +Essentially, your renderer needs to process edits and generate events to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen. + +Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves. + +For reference, check out the [javascript interpreter](https://github.com/DioxusLabs/dioxus/tree/main/packages/interpreter) or [tui renderer](https://github.com/DioxusLabs/blitz/tree/master/packages/dioxus-tui) as a starting point for your custom renderer. + +## Templates + +Dioxus is built around the concept of [Templates](https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html). Templates describe a UI tree known at compile time with dynamic parts filled at runtime. This is useful internally to make skip diffing static nodes, but it is also useful for the renderer to reuse parts of the UI tree. This can be useful for things like a list of items. Each item could contain some static parts and some dynamic parts. The renderer can use the template to create a static part of the UI once, clone it for each element in the list, and then fill in the dynamic parts. + +## Mutations + +The `Mutation` type is a serialized enum that represents an operation that should be applied to update the UI. The variants roughly follow this set: + +\```rust +enum Mutation { + AppendChildren, + AssignId, + CreatePlaceholder, + CreateTextNode, + HydrateText, + LoadTemplate, + ReplaceWith, + ReplacePlaceholder, + InsertAfter, + InsertBefore, + SetAttribute, + SetText, + NewEventListener, + RemoveEventListener, + Remove, + PushRoot, +} +\``` + +The Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the [LoadTemplate](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate), [CreatePlaceholder](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.CreatePlaceholder), and [CreateTextNode](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.CreateTextNode) mutations pushes a new "real" DOM node onto the stack and [AppendChildren](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.AppendChildren), [InsertAfter](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.InsertAfter), [InsertBefore](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.InsertBefore), [ReplacePlaceholder](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.ReplacePlaceholder), and [ReplaceWith](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.ReplaceWith) all remove nodes from the stack. + +## Node storage + +Dioxus saves and loads elements with IDs. Inside the VirtualDOM, this is just tracked as as a u64. + +Whenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when id is used in a mutation. Dioxus reclaims the IDs of elements when removed. To stay in sync with Dioxus you can use a sparse Vec (Vec>) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when an id does not exist. + +### An Example + +For the sake of understanding, let's consider this example – a very simple UI declaration: + +\```rust +rsx! { h1 { "count: {x}" } } +\``` + +#### Building Templates + +The above rsx will create a template that contains one static h1 tag and a placeholder for a dynamic text node. The template contains the static parts of the UI, and ids for the dynamic parts along with the paths to access them. + +The template will look something like this: + +\```rust +Template { + // Some id that is unique for the entire project + name: "main.rs:1:1:0", + // The root nodes of the template + roots: &[ + TemplateNode::Element { + tag: "h1", + namespace: None, + attrs: &[], + children: &[ + TemplateNode::DynamicText { + id: 0 + }, + ], + } + ], + // the path to each of the dynamic nodes + node_paths: &[ + // the path to dynamic node with a id of 0 + &[ + // on the first root node + 0, + // the first child of the root node + 0, + ] + ], + // the path to each of the dynamic attributes + attr_paths: &'a [&'a [u8]], +} +\``` + +> For more detailed docs about the structure of templates see the [Template api docs](https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html) + +This template will be sent to the renderer in the [list of templates](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Mutations.html#structfield.templates) supplied with the mutations the first time it is used. Any time the renderer encounters a [LoadTemplate](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate) mutation after this, it should clone the template and store it in the given id. + +For dynamic nodes and dynamic text nodes, a placeholder node should be created and inserted into the UI so that the node can be modified later. + +In HTML renderers, this template could look like this: + +\```html +

""

+\``` + +#### Applying Mutations + +After the renderer has created all of the new templates, it can begin to process the mutations. + +When the renderer starts, it should contain the Root node on the stack and store the Root node with an id of 0. The Root node is the top-level node of the UI. In HTML, this is the `
` element. + +\```rust +instructions: [] +stack: [ + RootNode, +] +nodes: [ + RootNode, +] +\``` + +The first mutation is a `LoadTemplate` mutation. This tells the renderer to load a root from the template with the given id. The renderer will then push the root node of the template onto the stack and store it with an id for later. In this case, the root node is an h1 element. + +\```rust +instructions: [ + LoadTemplate { + // the id of the template + name: "main.rs:1:1:0", + // the index of the root node in the template + index: 0, + // the id to store + id: ElementId(1), + } +] +stack: [ + RootNode, +

""

, +] +nodes: [ + RootNode, +

""

, +] +\``` + +Next, Dioxus will create the dynamic text node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the Mutation `HydrateText`. When the renderer receives this instruction, it will navigate to the placeholder text node in the template and replace it with the new text. + +\```rust +instructions: [ + LoadTemplate { + name: "main.rs:1:1:0", + index: 0, + id: ElementId(1), + }, + HydrateText { + // the id to store the text node + id: ElementId(2), + // the text to set + text: "count: 0", + } +] +stack: [ + RootNode, +

"count: 0"

, +] +nodes: [ + RootNode, +

"count: 0"

, + "count: 0", +] +\``` + +Remember, the h1 node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the h1 node to the Root. It depends on the situation, but in this case, we use `AppendChildren`. This pops the text node off the stack, leaving the Root element as the next element on the stack. + +\```rust +instructions: [ + LoadTemplate { + name: "main.rs:1:1:0", + index: 0, + id: ElementId(1), + }, + HydrateText { + id: ElementId(2), + text: "count: 0", + }, + AppendChildren { + // the id of the parent node + id: ElementId(0), + // the number of nodes to pop off the stack and append + m: 1 + } +] +stack: [ + RootNode, +] +nodes: [ + RootNode, +

"count: 0"

, + "count: 0", +] +\``` + +Over time, our stack looked like this: + +\```rust +[Root] +[Root,

""

] +[Root,

"count: 0"

] +[Root] +\``` + +Conveniently, this approach completely separates the Virtual DOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits make Dioxus independent of platform specifics. + +Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing. + +This little demo serves to show exactly how a Renderer would need to process a mutation stream to build UIs. + +## Event loop + +Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important for your custom renderer can handle those too. + +The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is: + +\```rust, ignore +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 + } +} +\``` + +It's important to decode what the real events are for your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `UserEvent` type. Right now, the virtual event system is modeled almost entirely around the HTML spec, but we are interested in slimming it down. + +\```rust, ignore +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(), + location: event.location(), + repeat: event.repeat(), + which: event.which(), + }) + }) + } + _ => todo!() + } +} +\``` + +## Custom raw elements + +If you need to go as far as relying on custom elements/attributes for your renderer – you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away. You can drop in your elements any time you want, with little hassle. However, you must be sure your renderer can handle the new namespace. + +For more examples and information on how to create custom namespaces, see the [`dioxus_html` crate](https://github.com/DioxusLabs/dioxus/blob/main/packages/html/README.md#how-to-extend-it). + +# Native Core + +If you are creating a renderer in rust, the [native-core](https://github.com/DioxusLabs/blitz/tree/master/packages/native-core) crate provides some utilities to implement a renderer. It provides an abstraction over Mutations and Templates and contains helpers that can handle the layout and text editing for you. + +## The RealDom + +The `RealDom` is a higher-level abstraction over updating the Dom. It uses an entity component system to manage the state of nodes. This system allows you to modify insert and modify arbitrary components on nodes. On top of this, the RealDom provides a way to manage a tree of nodes, and the State trait provides a way to automatically add and update these components when the tree is modified. It also provides a way to apply `Mutations` to the RealDom. + +### Example + +Let's build a toy renderer with borders, size, and text color. +Before we start let's take a look at an example element we can render: + +\```rust +rsx!{ + div{ + color: "red", + p{ + border: "1px solid black", + "hello world" + } + } +} +\``` + +In this tree, the color depends on the parent's color. The layout depends on the children's layout, the current text, and the text size. The border depends on only the current node. + +In the following diagram arrows represent dataflow: + +[![](https://mermaid.ink/img/pako:eNqllV1vgjAUhv8K6W4wkQVa2QdLdrHsdlfukmSptEhjoaSWqTH-9xVwONAKst70g5739JzzlO5BJAgFAYi52EQJlsr6fAszS7d1sVhKnCdWJDJFt6peLVs5-9owohK7HFrVcFJ_pxnpmK8VVvRkTJikkWIiaxy1dhP23bUwW1WW5WbPrrqJ4ziR4EJ6dtVN2ls5y1ZztePUcrWZFCvqVEcPPDffvlyS1XoLIQnVgnVvVPR6FU9Zc-6dV453ojjOPbuetRJ57gIeXQR3cez7rjtteZyZQ2j5MqmjqwE0ZW0VKx9RKtgpFewp1aw3sXXFy6TWgiYlv8mfq1scD8ofbBCAfQg8_AMBOAyBxzEIwA4CxgQ99QbQkjnD2KT7_CfxGF8_9WXQEsq5sDZCcjICOXRCri4h6r3NA38Q6Jdi1EOx5w3DGDYYI6MUvJFjM3VoGHUeGoMd6mBnDmh2E3fo7O4Yhf0x4OkBmIKUyhQzol_GfbkcApXQlIYg0EOC5SoEYXbQ-3ChxHyXRSBQsqBTUOREx_7OsAY3BUGM-VqvUsKUkB_1U6vf05gtweEHTk4_HQ?type=png)](https://mermaid.live/edit#pako:eNqllV1vgjAUhv8K6W4wkQVa2QdLdrHsdlfukmSptEhjoaSWqTH-9xVwONAKst70g5739JzzlO5BJAgFAYi52EQJlsr6fAszS7d1sVhKnCdWJDJFt6peLVs5-9owohK7HFrVcFJ_pxnpmK8VVvRkTJikkWIiaxy1dhP23bUwW1WW5WbPrrqJ4ziR4EJ6dtVN2ls5y1ZztePUcrWZFCvqVEcPPDffvlyS1XoLIQnVgnVvVPR6FU9Zc-6dV453ojjOPbuetRJ57gIeXQR3cez7rjtteZyZQ2j5MqmjqwE0ZW0VKx9RKtgpFewp1aw3sXXFy6TWgiYlv8mfq1scD8ofbBCAfQg8_AMBOAyBxzEIwA4CxgQ99QbQkjnD2KT7_CfxGF8_9WXQEsq5sDZCcjICOXRCri4h6r3NA38Q6Jdi1EOx5w3DGDYYI6MUvJFjM3VoGHUeGoMd6mBnDmh2E3fo7O4Yhf0x4OkBmIKUyhQzol_GfbkcApXQlIYg0EOC5SoEYXbQ-3ChxHyXRSBQsqBTUOREx_7OsAY3BUGM-VqvUsKUkB_1U6vf05gtweEHTk4_HQ) + +[//]: # "%% mermaid flow chart" +[//]: # "flowchart TB" +[//]: # " subgraph context" +[//]: # " text_width(text width)" +[//]: # " end" +[//]: # " subgraph state" +[//]: # " direction TB" +[//]: # " subgraph div state" +[//]: # " direction TB" +[//]: # " state1(state)---color1(color)" +[//]: # " linkStyle 0 stroke-width:10px;" +[//]: # " state1---border1(border)" +[//]: # " linkStyle 1 stroke-width:10px;" +[//]: # " text_width-.->layout_width1(layout width)" +[//]: # " linkStyle 2 stroke:#ff5500,stroke-width:4px;" +[//]: # " state1---layout_width1" +[//]: # " linkStyle 3 stroke-width:10px;" +[//]: # " end" +[//]: # " subgraph p state" +[//]: # " direction TB" +[//]: # " state2(state)---color2(color)" +[//]: # " linkStyle 4 stroke-width:10px;" +[//]: # " color1-.->color2" +[//]: # " linkStyle 5 stroke:#0000ff,stroke-width:4px;" +[//]: # " state2---border2(border)" +[//]: # " linkStyle 6 stroke-width:10px;" +[//]: # " text_width-.->layout_width2(layout width)" +[//]: # " linkStyle 7 stroke:#ff5500,stroke-width:4px;" +[//]: # " state2---layout_width2" +[//]: # " linkStyle 8 stroke-width:10px;" +[//]: # " layout_width2-.->layout_width1" +[//]: # " linkStyle 9 stroke:#00aa00,stroke-width:4px;" +[//]: # " end" +[//]: # " subgraph hello world state" +[//]: # " direction TB" +[//]: # " state3(state)---border3(border)" +[//]: # " linkStyle 10 stroke-width:10px;" +[//]: # " state3---color3(color)" +[//]: # " linkStyle 11 stroke-width:10px;" +[//]: # " color2-.->color3" +[//]: # " linkStyle 12 stroke:#0000ff,stroke-width:4px;" +[//]: # " text_width-.->layout_width3(layout width)" +[//]: # " linkStyle 13 stroke:#ff5500,stroke-width:4px;" +[//]: # " state3---layout_width3" +[//]: # " linkStyle 14 stroke-width:10px;" +[//]: # " layout_width3-.->layout_width2" +[//]: # " linkStyle 15 stroke:#00aa00,stroke-width:4px;" +[//]: # " end" +[//]: # " end" + +To help in building a Dom, native-core provides the State trait and a RealDom struct. The State trait provides a way to describe how states in a node depend on other states in its relatives. By describing how to update a single node from its relations, native-core will derive a way to update the states of all nodes for you. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom. + +Native Core cannot create all of the required methods for the State trait, but it can derive some of them. To implement the State trait, you must implement the following methods and let the `#[partial_derive_state]` macro handle the rest: + +\```rust, ignore +{{#include src/doc_examples/custom_renderer.rs:derive_state}} +\``` + +Lets take a look at how to implement the State trait for a simple renderer. + +\```rust +{{#include src/doc_examples/custom_renderer.rs:state_impl}} +\``` + +Now that we have our state, we can put it to use in our RealDom. We can update the RealDom with apply_mutations to update the structure of the dom (adding, removing, and changing properties of nodes) and then update_state to update the States for each of the nodes that changed. + +\```rust +{{#include src/doc_examples/custom_renderer.rs:rendering}} +\``` + +## Layout + +For most platforms, the layout of the Elements will stay the same. The [layout_attributes](https://docs.rs/dioxus-native-core/latest/dioxus_native_core/layout_attributes/index.html) module provides a way to apply HTML attributes a [Taffy](https://docs.rs/taffy/latest/taffy/index.html) layout style. + +## Text Editing + +To make it easier to implement text editing in rust renderers, `native-core` also contains a renderer-agnostic cursor system. The cursor can handle text editing, selection, and movement with common keyboard shortcuts integrated. + +\```rust +{{#include src/doc_examples/custom_renderer.rs:cursor}} +\``` + +## Conclusion + +That should be it! You should have nearly all the knowledge required on how to implement your renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the [community](https://discord.gg/XgGxMSkvUM). diff --git a/docs-src/0.5/zh/cookbook/error_handling.md b/docs-src/0.5/zh/cookbook/error_handling.md new file mode 100644 index 000000000..c4e891420 --- /dev/null +++ b/docs-src/0.5/zh/cookbook/error_handling.md @@ -0,0 +1,79 @@ +# Error handling + +A selling point of Rust for web development is the reliability of always knowing where errors can occur and being forced to handle them + +However, we haven't talked about error handling at all in this guide! In this chapter, we'll cover some strategies in handling errors to ensure your app never crashes. + + + +## The simplest – returning None + +Astute observers might have noticed that `Element` is actually a type alias for `Option`. You don't need to know what a `VNode` is, but it's important to recognize that we could actually return nothing at all: + +\```rust +{{#include src/doc_examples/error_handling.rs:none}} +\``` + +This lets us add in some syntactic sugar for operations we think *shouldn't* fail, but we're still not confident enough to "unwrap" on. + +> The nature of `Option` might change in the future as the `try` trait gets upgraded. + +\```rust +{{#include src/doc_examples/error_handling.rs:try_hook}} +\``` + +## Early return on result + +Because Rust can't accept both Options and Results with the existing try infrastructure, you'll need to manually handle Results. This can be done by converting them into Options or by explicitly handling them. If you choose to convert your Result into an Option and bubble it with a `?`, keep in mind that if you do hit an error you will lose error information and nothing will be rendered for that component. + +\```rust +{{#include src/doc_examples/error_handling.rs:try_result_hook}} +\``` + +Notice that while hooks in Dioxus do not like being called in conditionals or loops, they *are* okay with early returns. Returning an error state early is a completely valid way of handling errors. + + +## Match results + +The next "best" way of handling errors in Dioxus is to match on the error locally. This is the most robust way of handling errors, but it doesn't scale to architectures beyond a single component. + +To do this, we simply have an error state built into our component: + +\```rust +{{#include src/doc_examples/error_handling.rs:use_error}} +\``` + +Whenever we perform an action that generates an error, we'll set that error state. We can then match on the error in a number of ways (early return, return Element, etc). + + +\```rust +{{#include src/doc_examples/error_handling.rs:match_error}} +\``` + +## Passing error states through components + +If you're dealing with a handful of components with minimal nesting, you can just pass the error handle into child components. + +\```rust +{{#include src/doc_examples/error_handling.rs:match_error_children}} +\``` + +Much like before, our child components can manually set the error during their own actions. The advantage to this pattern is that we can easily isolate error states to a few components at a time, making our app more predictable and robust. + +## Throwing errors + +Dioxus provides a much easier way to handle errors: throwing them. Throwing errors combines the best parts of an error state and early return: you can easily throw and error with `?`, but you keep information about the error so that you can handle it in a parent component. + +You can call `throw` on any `Result` type that implements `Debug` to turn it into an error state and then use `?` to return early if you do hit an error. You can capture the error state with an `ErrorBoundary` component that will render the a different component if an error is thrown in any of its children. + +\```rust +{{#include src/doc_examples/error_handling.rs:throw_error}} +\``` + +You can even nest `ErrorBoundary` components to capture errors at different levels of your app. + +\```rust +{{#include src/doc_examples/error_handling.rs:nested_throw}} +\``` + +This pattern is particularly helpful whenever your code generates a non-recoverable error. You can gracefully capture these "global" error states without panicking or handling state for each error yourself. diff --git a/docs-src/0.5/zh/cookbook/examples.md b/docs-src/0.5/zh/cookbook/examples.md new file mode 100644 index 000000000..c33348dca --- /dev/null +++ b/docs-src/0.5/zh/cookbook/examples.md @@ -0,0 +1,20 @@ +# Examples + +There's a *lot* of these, so if you're having trouble implementing something, or you just want to see cool things +that you can do with Dioxus, make sure to give these a look! + +Each of the examples in the main repository also has a permalink attached, in case the main one doesn't work. + However, permalinks lead to an older "archived" version, so if you're reading this a long time in the future in a galaxy far, far away... + The examples in the permalinks might not work. + +- [Main list](https://github.com/DioxusLabs/dioxus/tree/main/examples) - [permalink]((https://github.com/DioxusLabs/dioxus/tree/7eccc7a104df013b06c104fc1275450d2747e78c/examples)) - This is the largest list. +- Package-specific examples from the [main repository](https://github.com/DioxusLabs/dioxus/). To learn more about these packages, search them up on [crates.io](https://crates.io/), or navigate from the examples to the root of the package. + - [dioxus-web](https://github.com/DioxusLabs/dioxus/tree/main/packages/web/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/7eccc7a104df013b06c104fc1275450d2747e78c/packages/web/examples) + - [dioxus-fullstack](https://github.com/DioxusLabs/dioxus/tree/main/packages/fullstack/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/7eccc7a104df013b06c104fc1275450d2747e78c/packages/fullstack/examples) + - [dioxus-liveview](shttps://github.com/DioxusLabs/dioxus/tree/main/packages/liveview/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/7eccc7a104df013b06c104fc1275450d2747e78c/packages/liveview/examples) + - [dioxus-router](https://github.com/DioxusLabs/dioxus/tree/main/packages/router/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/7eccc7a104df013b06c104fc1275450d2747e78c/packages/router/examples) + - [dioxus-tui](https://github.com/DioxusLabs/blitz/tree/master/packages/dioxus-tui/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/e118648346f764f39261868ad13efcc2aeb2fb21/packages/dioxus-tui/examples) + - [plasmo](https://github.com/DioxusLabs/blitz/tree/master/packages/plasmo/examples) - [permalink](https://github.com/DioxusLabs/blitz/tree/e118648346f764f39261868ad13efcc2aeb2fb21/packages/plasmo/examples) + - [rsx-rosetta](https://github.com/DioxusLabs/dioxus/tree/main/packages/rsx-rosetta/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/7eccc7a104df013b06c104fc1275450d2747e78c/packages/rsx-rosetta/examples) + - [native-core](https://github.com/DioxusLabs/blitz/tree/master/packages/native-core/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/e118648346f764f39261868ad13efcc2aeb2fb21/packages/native-core/examples) + - [signals](https://github.com/DioxusLabs/dioxus/tree/main/packages/signals/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/7eccc7a104df013b06c104fc1275450d2747e78c/packages/signals/examples) diff --git a/docs-src/0.5/zh/cookbook/index.md b/docs-src/0.5/zh/cookbook/index.md new file mode 100644 index 000000000..94ed29285 --- /dev/null +++ b/docs-src/0.5/zh/cookbook/index.md @@ -0,0 +1,16 @@ +# Cookbook + +The cookbook contains common recipes for different patterns within Dioxus. + +There are a few different sections in the cookbook: + +- [Publishing](publishing.md) will teach you how to present your app in a variety of delicious forms. +- Explore the [Anti-patterns](antipatterns.md) section to discover what ingredients to avoid when preparing your application. +- Within [Error Handling](error_handling.md), we'll master the fine art of managing spoiled ingredients in Dioxus. +- Take a culinary journey through [State management](state/index.md), where we'll explore the world of handling local, global, and external state in Dioxus. +- [Integrations](integrations/index.md) will guide you how to seamlessly blend external libraries into your Dioxus culinary creations. +- [Testing](testing.md) explains how to examine the unique flavor of Dioxus-specific features, like components. +- [Examples](examples.md) is a curated list of delightful recipes that demonstrate the various ways of using Dioxus ingredients. +- [Tailwind](tailwind.md) reveals the secrets of combining your Tailwind and Dioxus ingredients into a complete meal. You will also learn about using other NPM ingredients (packages) with Dioxus. +- In the [Custom Renderer](custom_renderer.md) section, we embark on a cooking adventure, inventing new ways to cook with Dioxus! +- [Optimizing](optimizing.md) will show you how to maximize the quality of your ingredients. \ No newline at end of file diff --git a/docs-src/0.5/zh/cookbook/integrations/auth.md b/docs-src/0.5/zh/cookbook/integrations/auth.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/docs-src/0.5/zh/cookbook/integrations/auth.md @@ -0,0 +1 @@ + diff --git a/docs-src/0.5/zh/cookbook/integrations/database.md b/docs-src/0.5/zh/cookbook/integrations/database.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/docs-src/0.5/zh/cookbook/integrations/database.md @@ -0,0 +1 @@ + diff --git a/docs-src/0.5/zh/cookbook/integrations/index.md b/docs-src/0.5/zh/cookbook/integrations/index.md new file mode 100644 index 000000000..10d891574 --- /dev/null +++ b/docs-src/0.5/zh/cookbook/integrations/index.md @@ -0,0 +1,4 @@ +This section of the guide provides getting started guides for common tools used with Dioxus. + +- [Logging](./logging.md) +- [Internationalization](./internationalization.md) \ No newline at end of file diff --git a/docs-src/0.5/zh/cookbook/integrations/internationalization.md b/docs-src/0.5/zh/cookbook/integrations/internationalization.md new file mode 100644 index 000000000..1b90152a5 --- /dev/null +++ b/docs-src/0.5/zh/cookbook/integrations/internationalization.md @@ -0,0 +1,9 @@ +# Internationalization + +If you application supports multiple languages, the [Dioxus SDK](https://github.com/DioxusLabs/sdk) crate contains helpers to make working with translations in your application easier. + +## The full code for internationalization + +\```rust +{{#include src/doc_examples/i18n.rs}} +\``` \ No newline at end of file diff --git a/docs-src/0.5/zh/cookbook/integrations/logging.md b/docs-src/0.5/zh/cookbook/integrations/logging.md new file mode 100644 index 000000000..9b97093ca --- /dev/null +++ b/docs-src/0.5/zh/cookbook/integrations/logging.md @@ -0,0 +1,128 @@ +# Logging +Dioxus has a wide range of supported platforms, each with their own logging requirements. We'll discuss the different options available for your projects. + +#### The Tracing Crate +The [Tracing](https://crates.io/crates/tracing) crate is the logging interface that the Dioxus library uses. It is not required to use the Tracing crate, but you will not recieve logs from the Dioxus library. + +The Tracing crate provides a variety of simple `println`-like macros with varying levels of severity. +The available macros are as follows with the highest severity on the bottom: +\```rs +fn main() { + tracing::trace!("trace"); + tracing::debug!("debug"); + tracing::info!("info"); + tracing::warn!("warn"); + tracing::error!("error"); +} +\``` +All the loggers provided on this page are, besides configuration and initialization, interfaced using these macros. Often you will also utilize the Tracing crate's `Level` enum. This enum usually represents the maximum log severity you want your application to emit and can be loaded from a variety of sources such as configuration file, environment variable, and more. + +For more information, visit the Tracing crate's [docs](https://docs.rs/tracing/latest/tracing/). + +## Dioxus Logger +[Dioxus Logger](https://crates.io/crates/dioxus-logger) is a logging utility that will start the appropriate logger for the platform. Currently every platform except mobile is supported. + +To use Dioxus Logger, call the `init()` function: +\```rs +use tracing::Level; + +fn main() { + // Init logger + dioxus_logger::init(Level::INFO).expect("failed to init logger"); + // Dioxus launch code +} +\``` +The `dioxus_logger::init()` function initializes Dioxus Logger with the appropriate tracing logger using the default configuration and provided `Level`. + +#### Platform Intricacies +On web, Dioxus Logger will use [tracing-wasm](https://crates.io/crates/tracing-wasm). On Desktop and server-based targets, Dioxus Logger will use [tracing-subscriber](https://crates.io/crates/tracing-subscriber)'s `FmtSubscriber`. + +#### Final Notes +Dioxus Logger is the preferred logger to use with Dioxus if it suites your needs. There are more features to come and Dioxus Logger is planned to become an integral part of Dioxus. If there are any feature suggestions or issues with Dioxus Logger, feel free to reach out on the [Dioxus Discord Server](https://discord.gg/XgGxMSkvUM)! + +For more information, visit Dioxus Logger's [docs](https://docs.rs/dioxus-logger/latest/dioxus_logger/). + +## Desktop and Server +For Dioxus' desktop and server targets, you can generally use the logger of your choice. + + +Some popular options are: +- [tracing-subscriber](https://crates.io/crates/tracing-subscriber)'s `FmtSubscriber` for console output. +- [tracing-appender](https://crates.io/crates/tracing-appender) for logging to files. +- [tracing-bunyan-formatter](https://crates.io/crates/tracing-bunyan-formatter) for the Bunyan format. + +To keep this guide short, we will not be covering the usage of these crates. + +For a full list of popular tracing-based logging crates, visit [this](https://docs.rs/tracing/latest/tracing/#related-crates) list in the Tracing crate's docs. + +## Web +[tracing-wasm](https://crates.io/crates/tracing-wasm) is a logging interface that can be used with Dioxus' web platform. + +The easiest way to use WASM Logger is with the `set_as_global_default` function: +\```rs +fn main() { + // Init logger + tracing_wasm::set_as_global_default(); + // Dioxus code +} +\``` +This starts tracing with a `Level` of `Trace`. + +Using a custom `level` is a little trickier. We need to use the `WasmLayerConfigBuilder` and start the logger with `set_as_global_default_with_config()`: +\```rs +use tracing::Level; + +fn main() { + // Init logger + let tracing_config = tracing_wasm::WASMLayerConfigBuilder::new().set_max_level(Level::INFO).build(); + tracing_wasm::set_as_global_default_with_config(tracing_config); + // Dioxus code +} +\``` + +# Mobile +Unfortunately there are no tracing crates that work with mobile targets. As an alternative you can use the [log](https://crates.io/crates/log) crate. + +## Android +[Android Logger](https://crates.io/crates/android_logger) is a logging interface that can be used when targeting Android. Android Logger runs whenever an event `native_activity_create` is called by the Android system: +\```rs +use log::LevelFilter; +use android_logger::Config; + +fn native_activity_create() { + android_logger::init_once( + Config::default() + .with_max_level(LevelFilter::Info) + .with_tag("myapp"); + ); +} +\``` +The `with_tag()` is what your app's logs will show as. + +#### Viewing Logs +Android logs are sent to logcat. To use logcat through the Android debugger, run: +\```cmd +adb -d logcat +\``` +Your Android device will need developer options/usb debugging enabled. + +For more information, visit android_logger's [docs](https://docs.rs/android_logger/latest/android_logger/). + +## iOS +The current option for iOS is the [oslog](https://crates.io/crates/oslog) crate. + +\```rs +fn main() { + // Init logger + OsLogger::new("com.example.test") + .level_filter(LevelFilter::Debug) + .init() + .expect("failed to init logger"); + // Dioxus code +} +\``` + +#### Viewing Logs +You can view the emitted logs in Xcode. + +For more information, visit [oslog](https://crates.io/crates/oslog). diff --git a/docs-src/0.5/zh/cookbook/optimizing.md b/docs-src/0.5/zh/cookbook/optimizing.md new file mode 100644 index 000000000..979765ad3 --- /dev/null +++ b/docs-src/0.5/zh/cookbook/optimizing.md @@ -0,0 +1,145 @@ +# Optimizing + +*Note: This is written primarily for the web, but the main optimizations will work on other platforms too.* + +You might have noticed that Dioxus binaries are pretty big. +The WASM binary of a [TodoMVC app](https://github.com/tigerros/dioxus-todo-app) weighs in at 2.36mb! +Don't worry; we can get it down to a much more manageable 234kb. +This will get obviously lower over time. +With nightly features, you can even reduce the binary size of a hello world app to less than 100kb! + +We will also discuss ways to optimize your app for increased speed. + +However, certain optimizations will sacrifice speed for decreased binary size or the other way around. +That's what you need to figure out yourself. Does your app perform performance-intensive tasks, such as graphical processing or tons of DOM manipulations? +You could go for increased speed. In most cases, though, decreased binary size is the better choice, especially because Dioxus WASM binaries are quite large. + +To test binary sizes, we will use [this](https://github.com/tigerros/dioxus-todo-app) repository as a sample app. +The `no-optimizations` package will serve as the base, which weighs 2.36mb as of right now. + +Additional resources: +- [WASM book - Shrinking `.wasm` code size](https://rustwasm.github.io/docs/book/reference/code-size.html) +- [min-sized-rust](https://github.com/johnthagen/min-sized-rust) + +## Building in release mode + +This is the best way to optimize. In fact, the 2.36mb figure at the start of the guide is with release mode. +In debug mode, it's actually a whopping 32mb! It also increases the speed of your app. + +Thankfully, no matter what tool you're using to build your app, it will probably have a `--release` flag to do this. + +Using the [Dioxus CLI](https://dioxuslabs.com/learn/0.5/CLI) or [Trunk](https://trunkrs.dev/): +- Dioxus CLI: `dx build --release` +- Trunk: `trunk build --release` + +## UPX + +If you're not targeting web, you can use the [UPX](https://github.com/upx/upx) CLI tool to compress your executables. + +Setup: + +- Download a [release](https://github.com/upx/upx/releases) and extract the directory inside to a sensible location. +- Add the executable located in the directory to your path variable. + +You can run `upx --help` to get the CLI options, but you should also view `upx-doc.html` for more detailed information. +It's included in the extracted directory. + +An example command might be: `upx --best -o target/release/compressed.exe target/release/your-executable.exe`. + +## Build configuration + +*Note: Settings defined in `.cargo/config.toml` will override settings in `Cargo.toml`.* + +Other than the `--release` flag, this is the easiest way to optimize your projects, and also the most effective way, +at least in terms of reducing binary size. + +### Stable + +This configuration is 100% stable and decreases the binary size from 2.36mb to 310kb. +Add this to your `.cargo/config.toml`: + +\```toml +[profile.release] +opt-level = "z" +debug = false +lto = true +codegen-units = 1 +panic = "abort" +strip = true +incremental = false +\``` + +Links to the documentation of each value: +- [`opt-level`](https://doc.rust-lang.org/rustc/codegen-options/index.html#opt-level) +- [`debug`](https://doc.rust-lang.org/rustc/codegen-options/index.html#debuginfo) +- [`lto`](https://doc.rust-lang.org/rustc/codegen-options/index.html#lto) +- [`codegen-units`](https://doc.rust-lang.org/rustc/codegen-options/index.html#codegen-units) +- [`panic`](https://doc.rust-lang.org/rustc/codegen-options/index.html#panic) +- [`strip`](https://doc.rust-lang.org/rustc/codegen-options/index.html#strip) +- [`incremental`](https://doc.rust-lang.org/rustc/codegen-options/index.html#incremental) + +### Unstable + +This configuration contains some unstable features, but it should work just fine. +It decreases the binary size from 310kb to 234kb. +Add this to your `.cargo/config.toml`: + +\```toml +[unstable] +build-std = ["std", "panic_abort", "core", "alloc"] +build-std-features = ["panic_immediate_abort"] + +[build] +rustflags = [ + "-Clto", + "-Zvirtual-function-elimination", + "-Zlocation-detail=none" +] + +# Same as in the Stable section +[profile.release] +opt-level = "z" +debug = false +lto = true +codegen-units = 1 +panic = "abort" +strip = true +incremental = false +\``` + +*Note: The omitted space in each flag (e.g., `-Clto`) is intentional. It is not a typo.* + +The values in `[profile.release]` are documented in the [Stable](#stable) section. Links to the documentation of each value: +- [`[build.rustflags]`](https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags) +- [`-C lto`](https://doc.rust-lang.org/rustc/codegen-options/index.html#lto) +- [`-Z virtual-function-elimination`](https://doc.rust-lang.org/stable/unstable-book/compiler-flags/virtual-function-elimination.html) +- [`-Z location-detail`](https://doc.rust-lang.org/stable/unstable-book/compiler-flags/location-detail.html) + +## wasm-opt + +*Note: In the future, `wasm-opt` will be supported natively through the [Dioxus CLI](https://crates.io/crates/dioxus-cli).* + +`wasm-opt` is a tool from the [binaryen](https://github.com/WebAssembly/binaryen) library that optimizes your WASM files. +To use it, install a [binaryen release](https://github.com/WebAssembly/binaryen/releases) and run this command from the package directory: + +\``` +wasm-opt dist/assets/dioxus/APP_NAME_bg.wasm -o dist/assets/dioxus/APP_NAME_bg.wasm -Oz +\``` + +The `-Oz` flag specifies that `wasm-opt` should optimize for size. For speed, use `-O4`. + +## Improving Dioxus code + +Let's talk about how you can improve your Dioxus code to be more performant. + +It's important to minimize the number of dynamic parts in your `rsx`, like conditional rendering. +When Dioxus is rendering your component, it will skip parts that are the same as the last render. +That means that if you keep dynamic rendering to a minimum, your app will speed up, and quite a bit if it's not just hello world. +To see an example of this, check out [Dynamic Rendering](../reference/dynamic_rendering.md). + +Also check out [Anti-patterns](antipatterns.md) for patterns that you should avoid. +Obviously, not all of them are just about performance, but some of them are. + +## Optimizing the size of assets + +Assets can be a significant part of your app's size. Dioxus includes alpha support for first party [assets](../reference/assets.md). Any assets you include with the `mg!` macro will be optimized for production in release builds. diff --git a/docs-src/0.5/zh/cookbook/publishing.md b/docs-src/0.5/zh/cookbook/publishing.md new file mode 100644 index 000000000..08f0e6571 --- /dev/null +++ b/docs-src/0.5/zh/cookbook/publishing.md @@ -0,0 +1,82 @@ +# Publishing + +After you have build your application, you will need to publish it somewhere. This reference will outline different methods of publishing your desktop or web application. + +## Web: Publishing with GitHub Pages + +Edit your `Dioxus.toml` to point your `out_dir` to the `docs` folder and the `base_path` to the name of your repo: + +\```toml +[application] +# ... +out_dir = "docs" + +[web.app] +base_path = "your_repo" +\``` + +Then build your app and publish it to Github: + +- Make sure GitHub Pages is set up for your repo to publish any static files in the docs directory +- Build your app with: +\```sh +dx build --release +\``` +- Make a copy of your `docs/index.html` file and rename the copy to `docs/404.html` so that your app will work with client-side routing +- Add and commit with git +- Push to GitHub + +## Desktop: Creating an installer + +Dioxus desktop app uses your operating system's WebView library, so it's portable to be distributed for other platforms. + +In this section, we'll cover how to bundle your app for macOS, Windows, and Linux. + +## Preparing your application for bundling + +Depending on your platform, you may need to add some additional code to your `main.rs` file to make sure your app is ready for bundling. On Windows, you'll need to add the `#![windows_subsystem = "windows"]` attribute to your `main.rs` file to hide the terminal window that pops up when you run your app. **If you're developing on Windows, only use this when bundling.** It will disable the terminal, so you will not get logs of any kind. You can gate it behind a feature, like so: + +\```toml +# Cargo.toml +[features] +bundle = [] +\``` + +And then your `main.rs`: + +\```rust +#![cfg_attr(feature = "bundle", windows_subsystem = "windows")] +\``` + +## Adding assets to your application + +If you want to bundle assets with your application, you can either use them with the `manganis` crate (covered more in the [assets](../reference/assets.md) page), or you can include them in your `Dioxus.toml` file: + +\```toml +[bundle] +# The list of files to include in the bundle. These can contain globs. +resources = ["main.css", "header.svg", "**/*.png"] +\``` + +## Install `dioxus CLI` + +The first thing we'll do is install the [dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/main/packages/cli). This extension to cargo will make it very easy to package our app for the various platforms. + +To install, simply run + +`cargo install dioxus-cli@0.5` + +## Building + +To bundle your application you can simply run `dx bundle --release` (also add `--features bundle` if you're using that, see the [this](#preparing-your-application-for-bundling) for more) to produce a final app with all the optimizations and assets builtin. + +Once you've ran the command, your app should be accessible in `dist/bundle/`. + +For example, a macOS app would look like this: + +![Published App](public/static/publish.png) + +Nice! And it's only 4.8 Mb – extremely lean!! Because Dioxus leverages your platform's native WebView, Dioxus apps are extremely memory efficient and won't waste your battery. + +> Note: not all CSS works the same on all platforms. Make sure to view your app's CSS on each platform – or web browser (Firefox, Chrome, Safari) before publishing. + diff --git a/docs-src/0.5/zh/cookbook/state/custom_hooks/index.md b/docs-src/0.5/zh/cookbook/state/custom_hooks/index.md new file mode 100644 index 000000000..85433a76e --- /dev/null +++ b/docs-src/0.5/zh/cookbook/state/custom_hooks/index.md @@ -0,0 +1,47 @@ +# Custom Hooks + +Hooks are a great way to encapsulate business logic. If none of the existing hooks work for your problem, you can write your own. + +When writing your hook, you can make a function that starts with `use_` and takes any arguments you need. You can then use the `use_hook` method to create a hook that will be called the first time the component is rendered. + +## Composing Hooks + +To avoid repetition, you can encapsulate business logic based on existing hooks to create a new hook. + +For example, if many components need to access an `AppSettings` struct, you can create a "shortcut" hook: + +\```rust +{{#include src/doc_examples/hooks_composed.rs:wrap_context}} +\``` + +Or if you want to wrap a hook that persists reloads with the storage API, you can build on top of the use_signal hook to work with mutable state: + +\```rust +{{#include src/doc_examples/hooks_composed.rs:use_storage}} +\``` + +## Custom Hook Logic + +You can use [`use_hook`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_hook.html) to build your own hooks. In fact, this is what all the standard hooks are built on! + +`use_hook` accepts a single closure for initializing the hook. It will be only run the first time the component is rendered. The return value of that closure will be used as the value of the hook – Dioxus will take it, and store it for as long as the component is alive. On every render (not just the first one!), you will get a reference to this value. + +> Note: You can use the `use_on_destroy` hook to clean up any resources the hook uses when the component is destroyed. + +Inside the initialization closure, you will typically make calls to other `cx` methods. For example: + +- The `use_signal` hook tracks state in the hook value, and uses [`schedule_update`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.schedule_update.html) to make Dioxus re-render the component whenever it changes. + +Here is a simplified implementation of the `use_signal` hook: + +\```rust +{{#include src/doc_examples/hooks_custom_logic.rs:use_signal}} +\``` + +- The `use_context` hook calls [`consume_context`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.consume_context.html) (which would be expensive to call on every render) to get some context from the component + +Here is an implementation of the `use_context` and `use_context_provider` hooks: + +\```rust +{{#include src/doc_examples/hooks_custom_logic.rs:use_context}} +\``` diff --git a/docs-src/0.5/zh/cookbook/state/external/index.md b/docs-src/0.5/zh/cookbook/state/external/index.md new file mode 100644 index 000000000..23a2a8021 --- /dev/null +++ b/docs-src/0.5/zh/cookbook/state/external/index.md @@ -0,0 +1,22 @@ +# Working with External State + +This guide will help you integrate your Dioxus application with some external state like a different thread or a websocket connection. + +## Working with non-reactive State + +[Coroutines](../../reference/use_coroutine.md) are great tool for dealing with non-reactive (state you don't render directly) state within your application. + + +You can store your state inside the coroutine async block and communicate with the coroutine with messages from any child components. + +\```rust +{{#include src/doc_examples/use_coroutine.rs:use_coroutine}} +\``` + +## Making Reactive State External + +If you have some reactive state (state that is rendered), that you want to modify from another thread, you can use a signal that is sync. Signals take an optional second generic value with information about syncness. Sync signals have a slightly higher overhead than thread local signals, but they can be used in a multithreaded environment. + +\```rust +{{#include src/doc_examples/sync_signal.rs}} +\``` diff --git a/docs-src/0.5/zh/cookbook/state/index.md b/docs-src/0.5/zh/cookbook/state/index.md new file mode 100644 index 000000000..1a7aaf63f --- /dev/null +++ b/docs-src/0.5/zh/cookbook/state/index.md @@ -0,0 +1,4 @@ +# State Cookbook + +- [External State](external/index.md.md) +- [Custom Hook](custom_hooks/index.md) \ No newline at end of file diff --git a/docs-src/0.5/zh/cookbook/tailwind.md b/docs-src/0.5/zh/cookbook/tailwind.md new file mode 100644 index 000000000..4efc3c37e --- /dev/null +++ b/docs-src/0.5/zh/cookbook/tailwind.md @@ -0,0 +1,100 @@ +# Tailwind + +You can style your Dioxus application with whatever CSS framework you choose, or just write vanilla CSS. + + +One popular option for styling your Dioxus application is [Tailwind](https://tailwindcss.com/). Tailwind allows you to style your elements with CSS utility classes. This guide will show you how to setup tailwind CSS with your Dioxus application. + +## Setup + +1. Install the Dioxus CLI: + +\```bash +cargo install dioxus-cli@0.5 +\``` + +2. Install npm: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm +3. Install the tailwind css cli: https://tailwindcss.com/docs/installation +4. Initialize the tailwind css project: + +\```bash +npx tailwindcss init +\``` + +This should create a `tailwind.config.js` file in the root of the project. + +5. Edit the `tailwind.config.js` file to include rust files: + +\```js +module.exports = { + mode: "all", + content: [ + // include all rust, html and css files in the src directory + "./src/**/*.{rs,html,css}", + // include all html files in the output (dist) directory + "./dist/**/*.html", + ], + theme: { + extend: {}, + }, + plugins: [], +} +\``` + +6. Create a `input.css` file in the root of your project with the following content: + +\```css +@tailwind base; +@tailwind components; +@tailwind utilities; +\``` + +7. Add [Manganis](https://github.com/DioxusLabs/collect-assets) to your project to handle asset collection. + +\```sh +cargo add manganis +\``` + +8. Create a link to the `tailwind.css` file using manganis somewhere in your rust code: + +\```rust +{{#include src/doc_examples/tailwind.rs}} +\``` + +### Bonus Steps + +1. Install the tailwind css vs code extension +2. Go to the settings for the extension and find the experimental regex support section. Edit the setting.json file to look like this: + +\```json +"tailwindCSS.experimental.classRegex": ["class: \"(.*)\""], +"tailwindCSS.includeLanguages": { + "rust": "html" +}, +\``` + +## Development + +- Run the following command in the root of the project to start the tailwind css compiler: + +\```bash +npx tailwindcss -i ./input.css -o ./public/tailwind.css --watch +\``` + +### Web + +- Run the following command in the root of the project to start the dioxus dev server: + +\```bash +dx serve +\``` + +- Open the browser to [http://localhost:8080](http://localhost:8080). + +### Desktop + +- Launch the dioxus desktop app: + +\```bash +dx serve --platform desktop +\``` diff --git a/docs-src/0.5/zh/cookbook/testing.md b/docs-src/0.5/zh/cookbook/testing.md new file mode 100644 index 000000000..a6da34846 --- /dev/null +++ b/docs-src/0.5/zh/cookbook/testing.md @@ -0,0 +1,46 @@ +# Testing + +When building application or libraries with Dioxus, you may want to include some tests to check the behavior of parts of your application. This guide will teach you how to test different parts of your Dioxus application. + +## Component Testing + +You can use a combination of [pretty-assertions](https://docs.rs/pretty_assertions/latest/pretty_assertions/) and [dioxus-ssr]() to check that two snippets of rsx are equal: + +\```rust +{{#include src/doc_examples/component_test.rs}} +\``` + +## Hook Testing + +When creating libraries around Dioxus, it can be helpful to make tests for your [custom hooks](./state/custom_hooks/index.md). + + +Dioxus does not currently have a full hook testing library, but you can build a bespoke testing framework by manually driving the virtual dom. + +\```rust +{{#include src/doc_examples/hook_test.rs}} +\``` + +## End to End Testing + +You can use [Playwright](https://playwright.dev/) to create end to end tests for your dioxus application. + +In your `playwright.config.js`, you will need to run cargo run or dx serve instead of the default build command. Here is a snippet from the end to end web example: + +\```js +//... +webServer: [ + { + cwd: path.join(process.cwd(), 'playwright-tests', 'web'), + command: 'dx serve', + port: 8080, + timeout: 10 * 60 * 1000, + reuseExistingServer: !process.env.CI, + stdout: "pipe", + }, +], +\``` + +- [Web example](https://github.com/DioxusLabs/dioxus/tree/main/playwright-tests/web) +- [Liveview example](https://github.com/DioxusLabs/dioxus/tree/main/playwright-tests/liveview) +- [Fullstack example](https://github.com/DioxusLabs/dioxus/tree/main/playwright-tests/fullstack) diff --git a/docs-src/0.5/zh/getting_started/index.md b/docs-src/0.5/zh/getting_started/index.md new file mode 100644 index 000000000..b1c371440 --- /dev/null +++ b/docs-src/0.5/zh/getting_started/index.md @@ -0,0 +1,90 @@ +# Getting Started + +This section will help you set up your Dioxus project! + +## Prerequisites + +### An Editor + +Dioxus integrates very well with the [Rust-Analyzer LSP plugin](https://rust-analyzer.github.io) which will provide appropriate syntax highlighting, code navigation, folding, and more. + +### Rust + +Head over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler. + +We strongly recommend going through the [official Rust book](https://doc.rust-lang.org/book/ch01-00-getting-started.html) _completely_. However, we hope that a Dioxus app can serve as a great first Rust project. With Dioxus, you'll learn about: + +- Error handling +- Structs, Functions, Enums +- Closures +- Macros + +We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge of async, lifetimes, or smart pointers until you start building complex Dioxus apps. + +### Platform-specific dependencies + +Most platforms don't require any additional dependencies, but if you are targeting desktop, you can install the following dependencies: + +\```inject-dioxus +DesktopDependencies {} +\``` + +### Dioxus CLI + +Next, lets install the Dioxus CLI: + +\``` +cargo install dioxus-cli@0.5 +\``` + +If you get an OpenSSL error on installation, ensure the dependencies listed [here](https://docs.rs/openssl/latest/openssl/#automatic) are installed. + +## Create a new project + +You can create a new Dioxus project by running the following command and following the prompts: + +\```sh +dx new +\``` + +\```inject-dioxus +video { + "type": "video/mp4", + "name": "dx new demo", + autoplay: "true", + controls: "false", + r#loop: "true", + width: "800px", + muted: "true", + source { + src: manganis::mg!(file("./public/static/dioxus-new.mov")), + } +} +\``` + +First you will need to select a platform. Each platform has its own reference with more information on how to set up a project for that platform. Here are the platforms we recommend starting with: + +- Web + - [Client Side](../reference/web/index.md): runs in the browser through WebAssembly + - [Fullstack](../reference/fullstack/index.md): renders to HTML text on the server and hydrates it on the client +> If you are not sure which web platform you want to use, check out the [choosing a web renderer](choosing_a_web_renderer.md) chapter. +- WebView + - [Desktop](../reference/desktop/index.md): runs in a web view on desktop + - [Mobile](../reference/mobile/index.md): runs in a web view on mobile. Mobile is currently not supported by the dioxus CLI. The [mobile reference](../reference/mobile/index.md) has more information about setting up a mobile project + +Next, you can choose a styling library. For this project, we will use vanilla CSS. + +Finally, you can choose to start the project with the router enabled. The router is covered in the [router guide](../router/index.md). + +## Running the project + +Once you have created your project, you can start it with the following command: + +\```sh +cd my_project +dx serve +\``` + +## Conclusion + +That's it! You now have a working Dioxus project. You can continue learning about dioxus by [making a hackernews clone in the guide](../guide/index.md), or learning about specific topics/platforms in the [reference](../reference/index.md). If you have any questions, feel free to ask in the [discord](https://discord.gg/XgGxMSkvUM) or [open a discussion](https://github.com/DioxusLabs/dioxus/discussions). diff --git a/docs-src/0.5/zh/guide/data_fetching.md b/docs-src/0.5/zh/guide/data_fetching.md new file mode 100644 index 000000000..fd8e37cd9 --- /dev/null +++ b/docs-src/0.5/zh/guide/data_fetching.md @@ -0,0 +1,69 @@ +# Fetching Data + +In this chapter, we will fetch data from the hacker news API and use it to render the list of top posts in our application. + +## Defining the API + +First we need to create some utilities to fetch data from the hackernews API using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html): + +\```rust +{{#include src/doc_examples/hackernews_async.rs:api}} +\``` + +The code above requires you to add the [reqwest](https://crates.io/crates/reqwest), [async_recursion](https://crates.io/crates/async-recursion), and [futures](https://crates.io/crates/futures) crate: + +\```bash +cargo add reqwest --features json +cargo add async_recursion +cargo add futures +\``` + +A quick overview of the supporting crates: +- [reqwest](https://crates.io/crates/reqwest) allows us to create HTTP calls to the hackernews API. +- [async_recursion](https://crates.io/crates/async-recursion) provides a utility macro to allow us to recursively use an async function. +- [futures](https://crates.io/crates/futures) provides us with utilities all around Rust's futures. + + +## Working with Async + +[`use_resource`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_resource.html) is a [hook](./state.md) that lets you run an async closure, and provides you with its result. + +For example, we can make an API request (using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html)) inside `use_resource`: + +\```rust +{{#include src/doc_examples/hackernews_async.rs:use_resource}} +\``` + +The code inside `use_resource` will be submitted to the Dioxus scheduler once the component has rendered. + +We can use `.read()` to get the result of the future. On the first run, since there's no data ready when the component loads, its value will be `None`. However, once the future is finished, the component will be re-rendered and the value will now be `Some(...)`, containing the return value of the closure. + +We can then render the result by looping over each of the posts and rendering them with the `StoryListing` component. + +\```inject-dioxus +DemoFrame { + hackernews_async::fetch::App {} +} +\``` + +> You can read more about working with Async in Dioxus in the [Async reference](../reference/index.md) + +## Lazily Fetching Data + +Finally, we will lazily fetch the comments on each post as the user hovers over the post. + + +We need to revisit the code that handles hovering over an item. Instead of passing an empty list of comments, we can fetch all the related comments when the user hovers over the item. + + +We will cache the list of comments with a [use_signal](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_signal.html) hook. This hook allows you to store some state in a single component. When the user triggers fetching the comments we will check if the response has already been cached before fetching the data from the hackernews API. + +\```rust +{{#include src/doc_examples/hackernews_async.rs:resolve_story}} +\``` + +\```inject-dioxus +DemoFrame { + hackernews_async::App {} +} +\``` diff --git a/docs-src/0.5/zh/guide/full_code.md b/docs-src/0.5/zh/guide/full_code.md new file mode 100644 index 000000000..50f1b4ff3 --- /dev/null +++ b/docs-src/0.5/zh/guide/full_code.md @@ -0,0 +1,18 @@ +# Conclusion + +Well done! You've completed the Dioxus guide and built a hackernews application in Dioxus. + +To continue your journey, you can attempt a challenge listed below, or look at the [Dioxus reference](../reference/index.md). + +## Challenges + +- Organize your components into separate files for better maintainability. +- Give your app some style if you haven't already. +- Integrate your application with the [Dioxus router](../router/index.md). + + +## The full code for the hacker news project + +\```rust +{{#include src/doc_examples/hackernews_complete.rs}} +\``` diff --git a/docs-src/0.5/zh/guide/index.md b/docs-src/0.5/zh/guide/index.md new file mode 100644 index 000000000..b68e2a914 --- /dev/null +++ b/docs-src/0.5/zh/guide/index.md @@ -0,0 +1,27 @@ +# Dioxus Guide + +## Introduction + +In this guide, you'll learn to use Dioxus to build user interfaces that run anywhere. We will recreate the hackernews homepage in Dioxus: + +\```inject-dioxus +DemoFrame { + hackernews_complete::App {} +} +\``` + +This guide serves a very brief overview of Dioxus. Throughout the guide, there will be links to the [reference](../reference/index.md) with more details about specific concepts. + +## Setup +Before you start the guide, make sure you have the dioxus CLI and a project set up for your platform as described in the [getting started](../getting_started/index.md) guide. + +First, lets setup our dependencies. In addition to the dependencies you added in the [getting started](../getting_started/index.md) guide for your platform, we need to set up a few more dependencies to work with the hacker news API: + +\```sh +cargo add chrono --features serde +cargo add futures +cargo add reqwest --features json +cargo add serde --features derive +cargo add serde_json +cargo add async_recursion +\``` diff --git a/docs-src/0.5/zh/guide/state.md b/docs-src/0.5/zh/guide/state.md new file mode 100644 index 000000000..5073fb2e2 --- /dev/null +++ b/docs-src/0.5/zh/guide/state.md @@ -0,0 +1,92 @@ +# Interactivity + +In this chapter, we will add a preview for articles you hover over or links you focus on. + +## Creating a Preview + +First, let's split our app into a Stories component on the left side of the screen, and a preview component on the right side of the screen: + +\```rust +{{#include src/doc_examples/hackernews_state.rs:app_v1}} +\``` + +\```inject-dioxus +DemoFrame { + hackernews_state::app_v1::App {} +} +\``` + +## Event Handlers + +Next, we need to detect when the user hovers over a section or focuses a link. We can use an [event listener](../reference/event_handlers.md) to listen for the hover and focus events. + +Event handlers are similar to regular attributes, but their name usually starts with `on`- and they accept closures as values. The closure will be called whenever the event it listens for is triggered. When an event is triggered, information about the event is passed to the closure though the [Event](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Event.html) structure. + +Let's create a [`onmouseenter`](https://docs.rs/dioxus/latest/dioxus/events/fn.onmouseenter.html) event listener in the `StoryListing` component: + +\```rust +{{#include src/doc_examples/hackernews_state.rs:story_listing_listener}} +\``` + +> You can read more about Event Handlers in the [Event Handler reference](../reference/event_handlers.md) + +## State + +So far our components have had no state like normal rust functions. To make our application change when we hover over a link we need state to store the currently hovered link in the root of the application. + +You can create state in dioxus using hooks. Hooks are Rust functions you call in a constant order in a component that add additional functionality to the component. + +In this case, we will use the `use_context_provider` and `use_context` hooks: + +- You can provide a closure to `use_context_provider` that determines the initial value of the shared state and provides the value to all child components +- You can then use the `use_context` hook to read and modify that state in the `Preview` and `StoryListing` components +- When the value updates, the `Signal` will cause the component to re-render, and provides you with the new value + +> Note: You should prefer local state hooks like use_signal or use_signal_sync when you only use state in one component. Because we use state in multiple components, we can use a [global state pattern](../reference/context.md) + +\```rust +{{#include src/doc_examples/hackernews_state.rs:shared_state_app}} +\``` + +\```rust +{{#include src/doc_examples/hackernews_state.rs:shared_state_stories}} +\``` + +\```rust +{{#include src/doc_examples/hackernews_state.rs:shared_state_preview}} +\``` + +\```inject-dioxus +DemoFrame { + hackernews_state::App {} +} +\``` + +> You can read more about Hooks in the [Hooks reference](../reference/hooks.md) + +### The Rules of Hooks + +Hooks are a powerful way to manage state in Dioxus, but there are some rules you need to follow to insure they work as expected. Dioxus uses the order you call hooks to differentiate between hooks. Because the order you call hooks matters, you must follow these rules: + +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 + 1. The same hooks must be called + 2. In the same order +3. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions + +These rules mean that there are certain things you can't do with hooks: + +#### No Hooks in Conditionals +\```rust +{{#include src/doc_examples/hooks_bad.rs:conditional}} +\``` + +#### No Hooks in Closures +\```rust +{{#include src/doc_examples/hooks_bad.rs:closure}} +\``` + +#### No Hooks in Loops +\```rust +{{#include src/doc_examples/hooks_bad.rs:loop}} +\``` diff --git a/docs-src/0.5/zh/guide/your_first_component.md b/docs-src/0.5/zh/guide/your_first_component.md new file mode 100644 index 000000000..096c09e80 --- /dev/null +++ b/docs-src/0.5/zh/guide/your_first_component.md @@ -0,0 +1,173 @@ +# Your First Component + +This chapter will teach you how to create a [Component](../reference/components.md) that displays a link to a post on hackernews. + +First, let's define how to display a post. Dioxus is a *declarative* framework. This means that instead of telling Dioxus what to do (e.g. to "create an element" or "set the color to red") we simply *declare* how we want the UI to look. + +To declare what you want your UI to look like, you will need to use the `rsx` macro. Let's create a ``main`` function and an ``App`` component to show information about our story: + +\```rust +{{#include src/doc_examples/hackernews_post.rs:story_v1}} +\``` + +Before running your application, you will need to add Dioxus as a dependency: +\```bash +cargo add dioxus@0.5.0 --features web +\``` + +Now if you run your application you should see something like this: + +\```inject-dioxus +DemoFrame { + hackernews_post::story_v1::App {} +} +\``` + +> RSX mirrors HTML. Because of this you will need to know some html to use Dioxus. +> +> Here are some resources to help get you started learning HTML: +> - [MDN HTML Guide](https://developer.mozilla.org/en-US/docs/Learn/HTML) +> - [W3 Schools HTML Tutorial](https://www.w3schools.com/html/default.asp) +> +> In addition to HTML, Dioxus uses CSS to style applications. You can either use traditional CSS (what this guide uses) or use a tool like [tailwind CSS](https://tailwindcss.com/docs/installation): +> - [MDN Traditional CSS Guide](https://developer.mozilla.org/en-US/docs/Learn/HTML) +> - [W3 Schools Traditional CSS Tutorial](https://www.w3schools.com/css/default.asp) +> - [Tailwind tutorial](https://tailwindcss.com/docs/installation) (used with the [Tailwind setup example](https://github.com/DioxusLabs/dioxus/tree/main/examples/tailwind)) +> +> If you have existing html code, you can use the [translate](../CLI/translate.md) command to convert it to RSX. Or if you prefer to write html, you can use the [html! macro](https://github.com/DioxusLabs/dioxus-html-macro) to write html directly in your code. + +## Dynamic Text + +Let's expand our `App` component to include the story title, author, score, time posted, and number of comments. We can insert dynamic text in the render macro by inserting variables inside `{}`s (this works similarly to the formatting in the [println!](https://doc.rust-lang.org/std/macro.println.html) macro): + +\```rust +{{#include src/doc_examples/hackernews_post.rs:story_v2}} +\``` + +\```inject-dioxus +DemoFrame { + hackernews_post::story_v2::App {} +} +\``` + +## Creating Elements + +Next, let's wrap our post description in a [`div`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div). You can create HTML elements in Dioxus by putting a `{` after the element name and a `}` after the last child of the element: + +\```rust +{{#include src/doc_examples/hackernews_post.rs:story_v3}} +\``` + +\```inject-dioxus +DemoFrame { + hackernews_post::story_v3::App {} +} +\``` + +> You can read more about elements in the [rsx reference](../reference/rsx.md). + +## Setting Attributes + +Next, let's add some padding around our post listing with an attribute. + +Attributes (and [listeners](../reference/event_handlers.md)) modify the behavior or appearance of the element they are attached to. They are specified inside the `{}` brackets before any children, using the `name: value` syntax. You can format the text in the attribute as you would with a text node: + +\```rust +{{#include src/doc_examples/hackernews_post.rs:story_v4}} +\``` + +\```inject-dioxus +DemoFrame { + hackernews_post::story_v4::App {} +} +\``` + +> Note: All attributes defined in [`dioxus-html`](https://docs.rs/dioxus-html/latest/dioxus_html/) follow the snake_case naming convention. They transform their `snake_case` names to HTML's `camelCase` attributes. + +> Note: Styles can be used directly outside of the `style:` attribute. In the above example, `padding: "0.5rem"` is turned into `style="padding: 0.5rem"`. + +> You can read more about elements in the [attribute reference](../reference/rsx.md) + +## Creating a Component + +Just like you wouldn't want to write a complex program in a single, long, `main` function, you shouldn't build a complex UI in a single `App` function. Instead, you should break down the functionality of an app in logical parts called components. + +A component is a Rust function, named in UpperCamelCase, that takes a props parameter and returns an `Element` describing the UI it wants to render. In fact, our `App` function is a component! + +Let's pull our story description into a new component: + +\```rust +{{#include src/doc_examples/hackernews_post.rs:story_v5}} +\``` + +We can render our component like we would an element by putting `{}`s after the component name. Let's modify our `App` component to render our new StoryListing component: + +\```rust +{{#include src/doc_examples/hackernews_post.rs:app_v5}} +\``` + +\```inject-dioxus +DemoFrame { + hackernews_post::story_v5::App {} +} +\``` + +> You can read more about elements in the [component reference](../reference/components.md) + +## Creating Props + +Just like you can pass arguments to a function or attributes to an element, you can pass props to a component that customize its behavior! + +We can define arguments that components can take when they are rendered (called `Props`) by adding the `#[component]` macro before our function definition and adding extra function arguments. + +Currently, our `StoryListing` component always renders the same story. We can modify it to accept a story to render as a prop. + + +We will also define what a post is and include information for how to transform our post to and from a different format using [serde](https://serde.rs). This will be used with the hackernews API in a later chapter: + +\```rust +{{#include src/doc_examples/hackernews_post.rs:story_v6}} +\``` + +Make sure to also add [serde](https://serde.rs) as a dependency: + +\```bash +cargo add serde --features derive +cargo add serde_json +\``` + +We will also use the [chrono](https://crates.io/crates/chrono) crate to provide utilities for handling time data from the hackernews API: +\```bash +cargo add chrono --features serde +\``` + + +Now, let's modify the `App` component to pass the story to our `StoryListing` component like we would set an attribute on an element: + +\```rust +{{#include src/doc_examples/hackernews_post.rs:app_v6}} +\``` + +\```inject-dioxus +DemoFrame { + hackernews_post::story_v6::App {} +} +\``` + +> You can read more about Props in the [Props reference](../reference/component_props.md) + +## Cleaning Up Our Interface + +Finally, by combining elements and attributes, we can make our post listing much more appealing: + +Full code up to this point: + +\```rust +{{#include src/doc_examples/hackernews_post.rs:story_final}} +\``` + +\```inject-dioxus +DemoFrame { + hackernews_post::story_final::App {} +} +\``` diff --git a/docs-src/0.5/zh/index.md b/docs-src/0.5/zh/index.md new file mode 100644 index 000000000..41798553b --- /dev/null +++ b/docs-src/0.5/zh/index.md @@ -0,0 +1,46 @@ +# 介绍 + +Dioxus 是一个便携、高性能且符合人体工程学的框架,用于在 Rust 中构建跨平台用户界面。本指南将帮助您开始编写 Dioxus 应用程序,用于 Web、桌面、移动设备等多种平台。 + +\```rust +{{#include src/doc_examples/readme.rs}} +\``` + +\```inject-dioxus +DemoFrame { + readme::App {} +} +\``` + +Dioxus 受到 React 的很大启发。如果您了解 React,则开始使用 Dioxus 将会很轻松。 + +> 本指南假定您已经了解一些 [Rust](https://www.rust-lang.org/)!如果没有,我们建议先阅读 [*这本书*](https://doc.rust-lang.org/book/ch01-00-getting-started.html) 来学习 Rust。 + +## 特性 + +- 三行代码即可创建跨平台应用程序。(Web、桌面、服务器、移动设备等) +- 极具人体工程学且功能强大的状态管理,结合了 React、Solid 和 Svelte 的优点。 +- 全面的内联文档 - 对所有 HTML 元素、监听器和事件进行悬停和指南。 +- 高性能应用程序,[接近最快的 Web 框架](https://dioxuslabs.com/blog/templates-diffing),在桌面上具有原生速度。 +- 一流的异步支持。 + +### 多平台 + +Dioxus 是一个 *便携* 工具包,意味着核心实现可以在任何地方运行,无需平台相关的链接。与许多其他 Rust 前端工具包不同,Dioxus 并不与 WebSys 紧密关联。事实上,每个元素和事件监听器都可以在编译时进行替换。默认情况下,Dioxus 配备了启用了 `html` 功能的特性,但根据您的目标渲染器,这可以禁用。 + +目前,我们有几个一方渲染器: +- WebSys/Sledgehammer(用于 WASM):很好的支持 +- Tao/Tokio(用于桌面应用程序):良好的支持 +- Tao/Tokio(用于移动应用程序):支持较差 +- Fullstack(用于 SSR 和服务器功能):良好的支持 +- TUI/Plasmo(用于基于终端的应用程序):实验性的 + +## 稳定性 + +Dioxus 尚未达到稳定版本。 + +Web:由于 Web 是一个相当成熟的平台,我们预计基于 Web 的功能 API 变动会很少。 + +桌面:API 可能会变动,因为我们正在摸索比我们的 ElectronJS 对应项更好的模式。 + +Fullstack:API 可能会变动,因为我们正在摸索与服务器通信的最佳 API。 diff --git a/docs-src/0.5/zh/migration/fermi.md b/docs-src/0.5/zh/migration/fermi.md new file mode 100644 index 000000000..4e42c43a3 --- /dev/null +++ b/docs-src/0.5/zh/migration/fermi.md @@ -0,0 +1,42 @@ +# Fermi + +In dioxus 0.5, fermi atoms have been replaced with global signals and included in the main dioxus library. + + +The new global signals can be used directly without hooks and include additional functionality like global memos. + +Dioxus 0.4: +\```rust +use dioxus::prelude::*; +use fermi::*; + +static NAME: Atom = Atom(|_| "world".to_string()); +static NAMES: AtomRef> = AtomRef(|_| vec!["world".to_string()]); + +fn app(cx: Scope) -> Element { + use_init_atom_root(cx); + let set_name = use_set(cx, &NAME); + let names = use_atom_ref(cx, &NAMES); + + cx.render(rsx! { + button { + onclick: move |_| set_name("dioxus".to_string()), + "reset name" + } + "{names.read():?}" + }) +} +\``` + +Dioxus 0.5: +\```rust +{{#include src/doc_examples/migration_fermi.rs:intro}} +\``` + +## Memos + +Dioxus 0.5 introduces global memos which can be used to store computed values globally. + +\```rust +{{#include src/doc_examples/migration_fermi.rs:memos}} +\``` diff --git a/docs-src/0.5/zh/migration/hooks.md b/docs-src/0.5/zh/migration/hooks.md new file mode 100644 index 000000000..6c7aeb1f3 --- /dev/null +++ b/docs-src/0.5/zh/migration/hooks.md @@ -0,0 +1,59 @@ +# Hooks + +Dioxus now uses signals as the backing for its state management. Signals are a smarter, more flexible version of the `use_ref` hook. Signals now back many hooks in dioxus to provide a more consistent and flexible API. + +### State Hooks + +State hooks are now backed by signals. `use_state`, `use_ref`, and `use_shared_state` have been replaced with the `use_signal` hook. The `use_signal` hook is a more flexible and powerful version of the `use_ref` hook with smarter scopes that only subscribe to a signal if that signal is read within the scope. You can read more about the `use_signal` hook in the [State Migration](state.md) guide. + +### Async Hooks + +The `use_future` hook has been replaced with the `use_resource` hook. `use_resource` automatically subscribes to any signals that are read within the closure instead of using a tuple of dependencies. + +Dioxus 0.4: + +\```rust +fn MyComponent(cx: Scope) -> Element { + let state = use_state(cx, || 0); + let my_resource = use_future(cx, (**state,), |(state,)| async move { + // start a request that depends on the state + println!("{state}"); + }); + render! { + "{state}" + } +} +\``` + +Dioxus 0.5: + +\```rust +{{#include src/doc_examples/migration_hooks.rs:use_resource}} +\``` + +### Dependencies + +Some hooks including `use_effect` and `use_resource` now take a single closure with automatic subscriptions instead of a tuple of dependencies. You can read more about the `use_resource` hook in the [Hook Migration](hooks.md) guide. + +Dioxus 0.4: + +\```rust +fn HasDependencies(cx: Scope) -> Element { + let state = use_state(cx, || 0); + let my_resource = use_resource(cx, (**state,), |(state,)| async move { + println!("{state}"); + }); + let state_plus_one = use_memo(cx, (**state,), |(state,)| { + state() + 1 + }); + render! { + "{state_plus_one}" + } +} +\``` + +Dioxus 0.5: + +\```rust +{{#include src/doc_examples/migration_hooks.rs:dependencies}} +\``` diff --git a/docs-src/0.5/zh/migration/index.md b/docs-src/0.5/zh/migration/index.md new file mode 100644 index 000000000..ee572273b --- /dev/null +++ b/docs-src/0.5/zh/migration/index.md @@ -0,0 +1,194 @@ +# How to Upgrade to Dioxus 0.5 + +This guide will outline the API changes between the `0.4` and `0.5` releases. + +`0.5` has includes significant changes to hooks, props, and global state. + +## Cheat Sheet + +Here is a quick cheat sheet for the changes: + +### Scope + +Dioxus 0.4: +\```rust +fn app(cx: Scope) -> Element { + cx.use_hook(|| { + /*...*/ + }); + cx.provide_context({ + /*...*/ + }); + cx.spawn(async move { + /*...*/ + }); + cx.render(rsx! { + /*...*/ + }) +} +\``` +Dioxus 0.5: +\```rust +{{#include src/doc_examples/migration.rs:scope}} +\``` + +### Props + +Dioxus 0.4: +\```rust +#[component] +fn Comp(cx: Scope, name: String) -> Element { + // You pass in an owned prop, but inside the component, it is borrowed (name is the type &String inside the function) + let owned_name: String = name.clone(); + + cx.render(rsx! { + "Hello {owned_name}" + BorrowedComp { + "{name}" + } + ManualPropsComponent { + name: name + } + }) +} + +#[component] +fn BorrowedComp<'a>(cx: Scope<'a>, name: &'a str) -> Element<'a> { + cx.render(rsx! { + "Hello {name}" + }) +} + +#[derive(Props, PartialEq)] +struct ManualProps { + name: String +} + +fn ManualPropsComponent(cx: Scope) -> Element { + cx.render(rsx! { + "Hello {cx.props.name}" + }) +} +\``` + +Dioxus 0.5: +\```rust +{{#include src/doc_examples/migration.rs:props}} +\``` + +You can read more about the new props API in the [Props Migration](props.md) guide. + +### Futures + +Dioxus 0.4: +\```rust +use_future((dependency1, dependency2,), move |(dependency1, dependency2,)| async move { + /*use dependency1 and dependency2*/ +}); +\``` +Dioxus 0.5: +\```rust +{{#include src/doc_examples/migration.rs:futures}} +\``` + +Read more about the `use_resource` hook in the [Hook Migration](hooks.md) guide. + +### State Hooks + +Dioxus 0.4: +\```rust +let copy_state = use_state(cx, || 0); +let clone_local_state = use_ref(cx, || String::from("Hello")); +use_shared_state_provider(cx, || String::from("Hello")); +let clone_shared_state = use_shared_state::(cx); + +let copy_state_value = **copy_state; +let clone_local_state_value = clone_local_state.read(); +let clone_shared_state_value = clone_shared_state.read(); + +cx.render(rsx!{ + "{copy_state_value}" + "{clone_shared_state_value}" + "{clone_local_state_value}" + button { + onclick: move |_| { + copy_state.set(1); + *clone_local_state.write() = "World".to_string(); + *clone_shared_state.write() = "World".to_string(); + }, + "Set State" + } +}) +\``` + +Dioxus 0.5: + +\```rust +{{#include src/doc_examples/migration.rs:state}} +\``` + +Read more about the `use_signal` hook in the [State Migration](state.md) guide. + +### Fermi + +Dioxus 0.4: +\```rust +use dioxus::prelude::*; +use fermi::*; + +static NAME: Atom = Atom(|_| "world".to_string()); + +fn app(cx: Scope) -> Element { + use_init_atom_root(cx); + let name = use_read(cx, &NAME); + + cx.render(rsx! { + div { "hello {name}!" } + Child {} + ChildWithRef {} + }) +} + +fn Child(cx: Scope) -> Element { + let set_name = use_set(cx, &NAME); + + cx.render(rsx! { + button { + onclick: move |_| set_name("dioxus".to_string()), + "reset name" + } + }) +} + +static NAMES: AtomRef> = AtomRef(|_| vec!["world".to_string()]); + +fn ChildWithRef(cx: Scope) -> Element { + let names = use_atom_ref(cx, &NAMES); + + cx.render(rsx! { + div { + ul { + names.read().iter().map(|f| rsx!{ + li { "hello: {f}" } + }) + } + button { + onclick: move |_| { + let names = names.clone(); + cx.spawn(async move { + names.write().push("asd".to_string()); + }) + }, + "Add name" + } + } + }) +} +\``` + +Dioxus 0.5: +\```rust +{{#include src/doc_examples/migration.rs:fermi}} +\``` + +You can read more about global signals in the [Fermi migration guide](fermi.md). diff --git a/docs-src/0.5/zh/migration/props.md b/docs-src/0.5/zh/migration/props.md new file mode 100644 index 000000000..c9a870435 --- /dev/null +++ b/docs-src/0.5/zh/migration/props.md @@ -0,0 +1,82 @@ +# Props Migration + +In dioxus 0.4, props are passed into the component through the scope. In dioxus 0.5, props are passed into the component through the props struct directly. + +## Owned Props + +The props were borrowed with the lifetime from the scope. The props are cloned every render, and passed into the component as an owned value. + +Dioxus 0.4: +\```rust +#[component] +fn Comp(cx: Scope, name: String) -> Element { + // You pass in an owned prop, but inside the component, it is borrowed (name is the type &String inside the function) + let owned_name: String = name.clone(); + + cx.render(rsx! { + "Hello {owned_name}" + }) +} +\``` +Dioxus 0.5: +\```rust +{{#include src/doc_examples/migration_props.rs:owned_props}} +\``` + +Because props are cloned every render, making props Copy is recommended. You can easily make a field Copy by accepting `ReadOnlySignal` instead of `T` in the props struct: + +\```rust +{{#include src/doc_examples/migration_props.rs:copy_props}} +\``` + +## Borrowed Props + +Borrowed props are removed in dioxus 0.5. Mapped signals can act similarly to borrowed props if your props are borrowed from state. + +Dioxus 0.4: +\```rust +fn Parent(cx: Scope) -> Element { + let state = use_state(cx, || (1, "World".to_string())); + rsx! { + BorrowedComp { + name: &state.get().1 + } + } +} + +#[component] +fn BorrowedComp<'a>(cx: Scope<'a>, name: &'a str) -> Element<'a> { + rsx! { + "Hello {name}" + } +} +\``` + +Dioxus 0.5: +\```rust +{{#include src/doc_examples/migration_props.rs:borrowed_props}} +\``` + +## Manual Props + +Manual prop structs in dioxus 0.5 need to derive `Clone` in addition to `Props` and `PartialEq`: + +Dioxus 0.4: +\```rust +#[derive(Props, PartialEq)] +struct ManualProps { + name: String, +} + +// Functions accept the props directly instead of the scope +fn ManualPropsComponent(cx: Scope) -> Element { + render! { + "Hello {cx.props.name}" + } +} +\``` + +Dioxus 0.5: +\```rust +{{#include src/doc_examples/migration_props.rs:manual_props}} +\``` diff --git a/docs-src/0.5/zh/migration/state.md b/docs-src/0.5/zh/migration/state.md new file mode 100644 index 000000000..95ce2cfda --- /dev/null +++ b/docs-src/0.5/zh/migration/state.md @@ -0,0 +1,63 @@ +# State Migration + +The `use_state` and `use_ref` hooks have been replaced with the `use_signal` hook. The `use_signal` hook is a more flexible and powerful version of the `use_ref` hook with smarter scopes that only subscribe to a signal if that signal is read within the scope. + +With `use_state`, if you had this code: +\```rust +fn Parent(cx: Scope) -> Element { + let state = use_state(cx, || 0); + + render! { + Child { + state: state.clone() + } + } +} + +#[component] +fn Child(cx: Scope, state: UseState) -> Element { + render! { + "{state}" + } +} +\``` + +Parent would re-render every time the state changed even though only the child component was using the state. With the new `use_signal` hook, the parent would only re-render if the state was changed within the parent component: + +\```rust +{{#include src/doc_examples/migration_state.rs:use_signal}} +\``` +Only the child component will re-render when the state changes because only the child component is reading the state. + +## Context Based State + +The `use_shared_state_provider` and `use_shared_state` hooks have been replaced with using the `use_context_provider` and `use_context` hooks with a `Signal`: + +\```rust +{{#include src/doc_examples/migration_state.rs:context_signals}} +\``` + +Signals are smart enough to handle subscribing to the right scopes without a special shared state hook. + +## Opting Out of Subscriptions + +Some state hooks including `use_shared_state` and `use_ref` hooks had a function called `write_silent` in `0.4`. This function allowed you to update the state without triggering a re-render any subscribers. This function has been removed in `0.5`. + + +Instead, you can use the `peek` function to read the current value of a signal without subscribing to it. This inverts the subscription model so that you can opt out of subscribing to a signal instead of opting all subscribers out of updates: + +\```rust +{{#include src/doc_examples/migration_state.rs:peek}} +\``` + +`peek` gives you more fine-grained control over when you want to subscribe to a signal. This can be useful for performance optimizations and for updating state without re-rendering components. + +## Global State + +In `0.4`, the fermi crate provided a separate global state API called atoms. In `0.5`, the `Signal` type has been extended to provide a global state API. You can use the `Signal::global` function to create a global signal: + +\```rust +{{#include src/doc_examples/migration_state.rs:global_signals}} +\``` + +You can read more about global signals in the [Fermi migration guide](fermi.md). diff --git a/docs-src/0.5/zh/reference/assets.md b/docs-src/0.5/zh/reference/assets.md new file mode 100644 index 000000000..448b3bae3 --- /dev/null +++ b/docs-src/0.5/zh/reference/assets.md @@ -0,0 +1,53 @@ +# Assets + +> ⚠️ Support: Manganis is currently in alpha. API changes are planned and bugs are more likely + +Assets are files that are included in the final build of the application. They can be images, fonts, stylesheets, or any other file that is not a source file. Dioxus includes first class support for assets, and provides a simple way to include them in your application and automatically optimize them for production. + +Assets in dioxus are also compatible with libraries! If you are building a library, you can include assets in your library and they will be automatically included in the final build of any application that uses your library. + +First, you need to add the `manganis` crate to your `Cargo.toml` file: + +\```sh +cargo add manganis +\``` + +## Including images + +To include an asset in your application, you can simply wrap the path to the asset in a `mg!` call. For example, to include an image in your application, you can use the following code: + +\```rust +{{#include src/doc_examples/assets.rs:images}} +\``` + +You can also optimize, resize, and preload images using the `mg!` macro. Choosing an optimized file type (like WebP) and a reasonable quality setting can significantly reduce the size of your images which helps your application load faster. For example, you can use the following code to include an optimized image in your application: + +\```rust +{{#include src/doc_examples/assets.rs:optimized_images}} +\``` + +## Including arbitrary files + +In dioxus desktop, you may want to include a file with data for your application. You can use the `file` function to include arbitrary files in your application. For example, you can use the following code to include a file in your application: + +\```rust +{{#include src/doc_examples/assets.rs:arbitrary_files}} +\``` + +These files will be automatically included in the final build of your application, and you can use them in your application as you would any other file. + +## Including stylesheets + +You can include stylesheets in your application using the `mg!` macro. For example, you can use the following code to include a stylesheet in your application: + +\```rust +{{#include src/doc_examples/assets.rs:style_sheets}} +\``` + +> The [tailwind guide](../cookbook/tailwind.md) has more information on how to use tailwind with dioxus. + +## Conclusion + +Dioxus provides first class support for assets, and makes it easy to include them in your application. You can include images, arbitrary files, and stylesheets in your application, and dioxus will automatically optimize them for production. This makes it easy to include assets in your application and ensure that they are optimized for production. + +You can read more about assets and all the options available to optimize your assets in the [manganis documentation](https://docs.rs/manganis/0.2.2/manganis/). diff --git a/docs-src/0.5/zh/reference/choosing_a_web_renderer.md b/docs-src/0.5/zh/reference/choosing_a_web_renderer.md new file mode 100644 index 000000000..1b77a8219 --- /dev/null +++ b/docs-src/0.5/zh/reference/choosing_a_web_renderer.md @@ -0,0 +1,61 @@ +# Choosing a web renderer + +Dioxus has three different renderers that target the web: + +- [dioxus-web](web.md) allows you to render your application to HTML with [WebAssembly](https://rustwasm.github.io/docs/book/) on the client +- [dioxus-liveview](liveview.md) allows you to run your application on the server and render it to HTML on the client with a websocket +- [dioxus-fullstack](fullstack.md) allows you to initially render static HTML on the server and then update that HTML from the client with [WebAssembly](https://rustwasm.github.io/docs/book/) + +Each approach has its tradeoffs: + +### Dioxus Liveview + +- Liveview rendering communicates with the server over a WebSocket connection. It essentially moves all of the work that Client-side rendering does to the server. + +- This makes it **easy to communicate with the server, but more difficult to communicate with the client/browser APIS**. + +- Each interaction also requires a message to be sent to the server and back which can cause **issues with latency**. + +- Because Liveview uses a websocket to render, the page will be blank until the WebSocket connection has been established and the first renderer has been sent from the websocket. Just like with client side rendering, this can make the page **less SEO-friendly**. + +- Because the page is rendered on the server and the page is sent to the client piece by piece, you never need to send the entire application to the client. The initial load time can be faster than client-side rendering with large applications because Liveview only needs to send a constant small websocket script regardless of the size of the application. + +> Liveview is a good fit for applications that already need to communicate with the server frequently (like real time collaborative apps), but don't need to communicate with as many client/browser APIs. + +[![](https://mermaid.ink/img/pako:eNplULFOw0AM_RXLc7Mw3sBQVUIMRYgKdcli5ZzkRHIuPl8QqvrvXJICRXiy3nt-9-6dsRHP6DAZGe8CdUpjNd3VEcpsVT4SK1TVPRxYJ1YHL_yeOdkqWMGF3w4U32Y6nSQmXvknMQYNXW8g7bfk2JPBg0g3MCTmdH1rJhenx2is1FiYri43wJ8or3O2H1Liv0w3hw724kMb2MMzdcUYNziyjhR8-f15Pq3Reh65RldWzy3lwWqs46VIKZscPmODzjTzBvPJ__aFrqUhFZR9MNH92uhS7OULYSF1lw?type=png)](https://mermaid.live/edit#pako:eNplULFOw0AM_RXLc7Mw3sBQVUIMRYgKdcli5ZzkRHIuPl8QqvrvXJICRXiy3nt-9-6dsRHP6DAZGe8CdUpjNd3VEcpsVT4SK1TVPRxYJ1YHL_yeOdkqWMGF3w4U32Y6nSQmXvknMQYNXW8g7bfk2JPBg0g3MCTmdH1rJhenx2is1FiYri43wJ8or3O2H1Liv0w3hw724kMb2MMzdcUYNziyjhR8-f15Pq3Reh65RldWzy3lwWqs46VIKZscPmODzjTzBvPJ__aFrqUhFZR9MNH92uhS7OULYSF1lw) + +### Dioxus Web + +- With Client side rendering, you send your application to the client, and then the client generates all of the HTML of the page dynamically. + +- This means that the page will be blank until the JavaScript bundle has loaded and the application has initialized. This can result in **slower first render times and poor SEO performance**. + +> SEO stands for Search Engine Optimization. It refers to the practice of making your website more likely to appear in search engine results. Search engines like Google and Bing use web crawlers to index the content of websites. Most of these crawlers are not able to run JavaScript, so they will not be able to index the content of your page if it is rendered client-side. + +- Client-side rendered applications need to use **weakly typed requests to communicate with the server**. + +> Client-side rendering is a good starting point for most applications. It is well supported and makes it easy to communicate with the client/browser APIs. + +[![](https://mermaid.ink/img/pako:eNpVkDFPwzAQhf-KdXOzMHpgqJAQAwytEIsXK35JLBJfez4Xoar_HSemQtzke9_z2e-u1HMAWcrqFU_Rj-KX7vLgkqm1F_7KENN1j-YIuUCsOeBckLUZmrjx_ezT54rziVNG42-sMBLHSQ0Pd8vH5NU8M48zTAby71sr3CYdkAIEoen37h-y5n3910tSiO81cqIdLZDFx1DDXNerjnTCAke2HgMGX2Z15NKtWn1RPn6nnqxKwY7KKfzFJzv4OVcVISrLa1vQtqfbDzd0ZKY?type=png)](https://mermaid.live/edit#pako:eNpVkDFPwzAQhf-KdXOzMHpgqJAQAwytEIsXK35JLBJfez4Xoar_HSemQtzke9_z2e-u1HMAWcrqFU_Rj-KX7vLgkqm1F_7KENN1j-YIuUCsOeBckLUZmrjx_ezT54rziVNG42-sMBLHSQ0Pd8vH5NU8M48zTAby71sr3CYdkAIEoen37h-y5n3910tSiO81cqIdLZDFx1DDXNerjnTCAke2HgMGX2Z15NKtWn1RPn6nnqxKwY7KKfzFJzv4OVcVISrLa1vQtqfbDzd0ZKY) + +### Dioxus Fullstack + +Fullstack rendering happens in two parts: +1. The page is rendered on the server. This can include fetching any data you need to render the page. +2. The page is hydrated on the client. (Hydration is taking the HTML page from the server and adding all of the event listeners Dioxus needs on the client). Any updates to the page happen on the client after this point. + +Because the page is initially rendered on the server, the page will be fully rendered when it is sent to the client. This results in a faster first render time and makes the page more SEO-friendly. + +- **Fast initial render** +- **Works well with SEO** +- **Type safe easy communication with the server** +- **Access to the client/browser APIs** +- **Fast interactivity** + +Finally, we can use [server functions](../reference/fullstack/server_functions.md) to communicate with the server in a type-safe way. + +This approach uses both the dioxus-web and dioxus-ssr crates. To integrate those two packages Dioxus provides the `dioxus-fullstack` crate. + +There can be more complexity with fullstack applications because your code runs in two different places. Dioxus tries to mitigate this with server functions and other helpers. + +[![](https://mermaid.ink/img/pako:eNpdkL1uwzAMhF9F4BwvHTV0KAIUHdohQdFFi2CdbQG2mFCUiyDIu9e2-hOUE3H34UDelVoOIEtZvWIffS9-auYHl8wyT8KfGWKa5tEcITPEmgPOBVkrUMXNPyAFCMJK5BOnjIq8scJI7Ac13N1RH4NX88zcjzAZyJX-8bfIl6QQ32qcv7PuhP-ANe_rpb8KJ9rRBJl8DMt71zXAkQ6Y4Mgua0Dny6iOXLotqC_Kx0tqyaoU7Kicwl8hZDs_5kVFiMryWivbmrt9AacxbGg?type=png)](https://mermaid.live/edit#pako:eNpdkL1uwzAMhF9F4BwvHTV0KAIUHdohQdFFi2CdbQG2mFCUiyDIu9e2-hOUE3H34UDelVoOIEtZvWIffS9-auYHl8wyT8KfGWKa5tEcITPEmgPOBVkrUMXNPyAFCMJK5BOnjIq8scJI7Ac13N1RH4NX88zcjzAZyJX-8bfIl6QQ32qcv7PuhP-ANe_rpb8KJ9rRBJl8DMt71zXAkQ6Y4Mgua0Dny6iOXLotqC_Kx0tqyaoU7Kicwl8hZDs_5kVFiMryWivbmrt9AacxbGg) diff --git a/docs-src/0.5/zh/reference/component_props.md b/docs-src/0.5/zh/reference/component_props.md new file mode 100644 index 000000000..af9600614 --- /dev/null +++ b/docs-src/0.5/zh/reference/component_props.md @@ -0,0 +1,153 @@ +# Component Props + +Just like you can pass arguments to a function or attributes to an element, you can pass props to a component that customize its behavior! The components we've seen so far didn't accept any props – so let's write some components that do. + +## `#[derive(Props)]` + +Component props are a single struct annotated with `#[derive(PartialEq, Clone, Props)]`. For a component to accept props, the type of its argument must be `YourPropsStruct`. + +Example: + +\```rust, no_run +{{#include src/doc_examples/component_owned_props.rs:Likes}} +\``` + +You can then pass prop values to the component the same way you would pass attributes to an element: + +\```rust, no_run +{{#include src/doc_examples/component_owned_props.rs:App}} +\``` + +\```inject-dioxus +DemoFrame { + component_owned_props::App {} +} +\``` + +## Prop Options + +The `#[derive(Props)]` macro has some features that let you customize the behavior of props. + +### Optional Props + +You can create optional fields by using the `Option<…>` type for a field: + +\```rust, no_run +{{#include src/doc_examples/component_props_options.rs:OptionalProps}} +\``` + +Then, you can choose to either provide them or not: + +\```rust, no_run +{{#include src/doc_examples/component_props_options.rs:OptionalProps_usage}} +\``` + +### Explicitly Required Option + +If you want to explicitly require an `Option`, and not an optional prop, you can annotate it with `#[props(!optional)]`: + +\```rust, no_run +{{#include src/doc_examples/component_props_options.rs:ExplicitOption}} +\``` + +Then, you have to explicitly pass either `Some("str")` or `None`: + +\```rust, no_run +{{#include src/doc_examples/component_props_options.rs:ExplicitOption_usage}} +\``` + +### Default Props + +You can use `#[props(default = 42)]` to make a field optional and specify its default value: + +\```rust, no_run +{{#include src/doc_examples/component_props_options.rs:DefaultComponent}} +\``` + +Then, similarly to optional props, you don't have to provide it: + +\```rust, no_run +{{#include src/doc_examples/component_props_options.rs:DefaultComponent_usage}} +\``` + +### Automatic Conversion with into + +It is common for Rust functions to accept `impl Into` rather than just `SomeType` to support a wider range of parameters. If you want similar functionality with props, you can use `#[props(into)]`. For example, you could add it on a `String` prop – and `&str` will also be automatically accepted, as it can be converted into `String`: + +\```rust, no_run +{{#include src/doc_examples/component_props_options.rs:IntoComponent}} +\``` + +Then, you can use it so: + +\```rust, no_run +{{#include src/doc_examples/component_props_options.rs:IntoComponent_usage}} +\``` + +## The component macro + +So far, every Component function we've seen had a corresponding ComponentProps struct to pass in props. This was quite verbose... Wouldn't it be nice to have props as simple function arguments? Then we wouldn't need to define a Props struct, and instead of typing `props.whatever`, we could just use `whatever` directly! + +`component` allows you to do just that. Instead of typing the "full" version: + +\```rust, no_run +#[derive(Props, Clone, PartialEq)] +struct TitleCardProps { + title: String, +} + +fn TitleCard(props: TitleCardProps) -> Element { + rsx!{ + h1 { "{props.title}" } + } +} +\``` + +...you can define a function that accepts props as arguments. Then, just annotate it with `#[component]`, and the macro will turn it into a regular Component for you: + +\```rust, no_run +#[component] +fn TitleCard(title: String) -> Element { + rsx!{ + h1 { "{title}" } + } +} +\``` + +> While the new Component is shorter and easier to read, this macro should not be used by library authors since you have less control over Prop documentation. + +## Component Children + +In some cases, you may wish to create a component that acts as a container for some other content, without the component needing to know what that content is. To achieve this, create a prop of type `Element`: + +\```rust, no_run +{{#include src/doc_examples/component_element_props.rs:Clickable}} +\``` + +Then, when rendering the component, you can pass in the output of `rsx!{...}`: + +\```rust, no_run +{{#include src/doc_examples/component_element_props.rs:Clickable_usage}} +\``` + +> Warning: While it may compile, do not include the same `Element` more than once in the RSX. The resulting behavior is unspecified. + +### The children field + +Rather than passing the RSX through a regular prop, you may wish to accept children similarly to how elements can have children. The "magic" `children` prop lets you achieve this: + +\```rust, no_run +{{#include src/doc_examples/component_children.rs:Clickable}} +\``` + +This makes using the component much simpler: simply put the RSX inside the `{}` brackets – and there is no need for a `render` call or another macro! + +\```rust, no_run +{{#include src/doc_examples/component_children.rs:Clickable_usage}} +\``` + +\```inject-dioxus +DemoFrame { + component_children::App {} +} +\``` diff --git a/docs-src/0.5/zh/reference/components.md b/docs-src/0.5/zh/reference/components.md new file mode 100644 index 000000000..6c395e088 --- /dev/null +++ b/docs-src/0.5/zh/reference/components.md @@ -0,0 +1,35 @@ +# Components + +Just like you wouldn't want to write a complex program in a single, long, `main` function, you shouldn't build a complex UI in a single `App` function. Instead, you should break down the functionality of an app in logical parts called components. + +A component is a Rust function, named in UpperCammelCase, that either takes no parameters or a properties struct and returns an `Element` describing the UI it wants to render. + +\```rust, no_run +{{#include src/doc_examples/hello_world_desktop.rs:component}} +\``` + +> You'll probably want to add `#![allow(non_snake_case)]` to the top of your crate to avoid warnings about UpperCammelCase component names + +A Component is responsible for some rendering task – typically, rendering an isolated part of the user interface. For example, you could have an `About` component that renders a short description of Dioxus Labs: + +\```rust, no_run +{{#include src/doc_examples/components.rs:About}} +\``` +\```inject-dioxus +DemoFrame { + components::About {} +} +\``` + +Then, you can render your component in another component, similarly to how elements are rendered: + +\```rust, no_run +{{#include src/doc_examples/components.rs:App}} +\``` +\```inject-dioxus +DemoFrame { + components::App {} +} +\``` + +> At this point, it might seem like components are nothing more than functions. However, as you learn more about the features of Dioxus, you'll see that they are actually more powerful! diff --git a/docs-src/0.5/zh/reference/context.md b/docs-src/0.5/zh/reference/context.md new file mode 100644 index 000000000..1cff8d157 --- /dev/null +++ b/docs-src/0.5/zh/reference/context.md @@ -0,0 +1,73 @@ +# Sharing State + +Often, multiple components need to access the same state. Depending on your needs, there are several ways to implement this. + +## Lifting State + +One approach to share state between components is to "lift" it up to the nearest common ancestor. This means putting the `use_signal` hook in a parent component, and passing the needed values down as props. + +Suppose we want to build a meme editor. We want to have an input to edit the meme caption, but also a preview of the meme with the caption. Logically, the meme and the input are 2 separate components, but they need access to the same state (the current caption). + +> Of course, in this simple example, we could write everything in one component – but it is better to split everything out in smaller components to make the code more reusable, maintainable, and performant (this is even more important for larger, complex apps). + +We start with a `Meme` component, responsible for rendering a meme with a given caption: + +\```rust, no_run +{{#include src/doc_examples/meme_editor.rs:meme_component}} +\``` + +> Note that the `Meme` component is unaware where the caption is coming from – it could be stored in `use_signal`, or a constant. This ensures that it is very reusable – the same component can be used for a meme gallery without any changes! + +We also create a caption editor, completely decoupled from the meme. The caption editor must not store the caption itself – otherwise, how will we provide it to the `Meme` component? Instead, it should accept the current caption as a prop, as well as an event handler to delegate input events to: + +\```rust, no_run +{{#include src/doc_examples/meme_editor.rs:caption_editor}} +\``` + +Finally, a third component will render the other two as children. It will be responsible for keeping the state and passing down the relevant props. + +\```rust, no_run +{{#include src/doc_examples/meme_editor.rs:meme_editor}} +\``` + +![Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: "me waiting for a language feature"](public/static/meme_editor_screenshot.png) + +## 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. + +Suppose now that we want to implement a dark mode toggle for our app. To achieve this, we will make every component select styling depending on whether dark mode is enabled or not. + +> Note: we're choosing this approach for the sake of an example. There are better ways to implement dark mode (e.g. using CSS variables). Let's pretend CSS variables don't exist – welcome to 2013! + +Now, we could write another `use_signal` 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 provides any Clone context (including Signals!) to any child components. Child components can use the [`use_context`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) hook to get that context and if it is a Signal, they can read and write to it. + +First, we have to create a struct for our dark mode configuration: + +\```rust, no_run +{{#include src/doc_examples/meme_editor_dark_mode.rs:DarkMode_struct}} +\``` + +Now, in a top-level component (like `App`), we can provide the `DarkMode` context to all children components: + +\```rust, no_run +{{#include src/doc_examples/meme_editor_dark_mode.rs:context_provider}} +\``` + +As a result, any child component of `App` (direct or not), can access the `DarkMode` context. + +\```rust, no_run +{{#include src/doc_examples/meme_editor_dark_mode.rs:use_context}} +\``` + +> `use_context` returns `Signal` here, because the Signal was provided by the parent. If the context hadn't been provided `use_context` would have panicked. + +If you have a component where the context might or not be provided, you might want to use `try_consume_context`instead, so you can handle the `None` case. The drawback of this method is that it will not memoize the value between renders, so it won't be as as efficient as `use_context`, you could do it yourself with `use_hook` though. + +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): + +\```rust, no_run +{{#include src/doc_examples/meme_editor_dark_mode.rs:toggle}} +\``` diff --git a/docs-src/0.5/zh/reference/desktop/index.md b/docs-src/0.5/zh/reference/desktop/index.md new file mode 100644 index 000000000..63328c151 --- /dev/null +++ b/docs-src/0.5/zh/reference/desktop/index.md @@ -0,0 +1,43 @@ +# Desktop + +This guide will cover concepts specific to the Dioxus desktop renderer. + +Apps built with Dioxus desktop use the system WebView to render the page. This makes the final size of application much smaller than other WebView renderers (typically under 5MB). + +Although desktop apps are rendered in a WebView, your Rust code runs natively. This means that browser APIs are _not_ available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs _are_ accessible, so streaming, WebSockets, filesystem, etc are all easily accessible though system APIs. + +Dioxus desktop is built off [Tauri](https://tauri.app/). Right now there are limited Dioxus abstractions over the menubar, event handling, etc. In some places you may need to leverage Tauri directly – through [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao). + +> In the future, we plan to move to a custom web renderer-based DOM renderer with WGPU integrations ([Blitz](https://github.com/DioxusLabs/blitz)). + +## Examples + +- [File Explorer](https://github.com/DioxusLabs/dioxus/blob/main/examples/file_explorer.rs) +- [Tailwind App](https://github.com/DioxusLabs/dioxus/tree/main/examples/tailwind) + +[![Tailwind App screenshot](./public/static/tailwind_desktop_app.png)](https://github.com/DioxusLabs/dioxus/tree/main/examples/tailwind) + +## Running Javascript + +Dioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose. + + +For these cases, Dioxus desktop exposes the use_eval hook that allows you to run raw Javascript in the webview: + +\```rust +{{#include src/doc_examples/eval.rs}} +\``` + +## Custom Assets + +You can link to local assets in dioxus desktop instead of using a url: + +\```rust +{{#include src/doc_examples/custom_assets.rs}} +\``` + +You can read more about assets in the [assets](./assets.md) reference. + +## Integrating with Wry + +In cases where you need more low level control over your window, you can use wry APIs exposed through the [Desktop Config](https://docs.rs/dioxus-desktop/0.5.0/dioxus_desktop/struct.Config.html) and the [use_window hook](https://docs.rs/dioxus-desktop/0.5.0/dioxus_desktop/fn.use_window.html) diff --git a/docs-src/0.5/zh/reference/dynamic_rendering.md b/docs-src/0.5/zh/reference/dynamic_rendering.md new file mode 100644 index 000000000..20c6faeb7 --- /dev/null +++ b/docs-src/0.5/zh/reference/dynamic_rendering.md @@ -0,0 +1,112 @@ +# Dynamic Rendering + +Sometimes you want to render different things depending on the state/props. With Dioxus, just describe what you want to see using Rust control flow – the framework will take care of making the necessary changes on the fly if the state or props change! + +## Conditional Rendering + +To render different elements based on a condition, you could use an `if-else` statement: + +\```rust, no_run +{{#include src/doc_examples/conditional_rendering.rs:if_else}} +\``` +\```inject-dioxus +DemoFrame { + conditional_rendering::App {} +} +\``` + +> You could also use `match` statements, or any Rust function to conditionally render different things. + +### Improving the `if-else` Example + +You may have noticed some repeated code in the `if-else` example above. Repeating code like this is both bad for maintainability and performance. Dioxus will skip diffing static elements like the button, but when switching between multiple `rsx` calls it cannot perform this optimization. For this example either approach is fine, but for components with large parts that are reused between conditionals, it can be more of an issue. + +We can improve this example by splitting up the dynamic parts and inserting them where they are needed. + +\```rust, no_run +{{#include src/doc_examples/conditional_rendering.rs:if_else_improved}} +\``` +\```inject-dioxus +DemoFrame { + conditional_rendering::LogInImprovedApp {} +} +\``` + +### Inspecting `Element` props + +Since `Element` is a `Option`, components accepting `Element` as a prop can inspect its contents, and render different things based on that. Example: + +\```rust, no_run +{{#include src/doc_examples/component_children_inspect.rs:Clickable}} +\``` + +You can't mutate the `Element`, but if you need a modified version of it, you can construct a new one based on its attributes/children/etc. + +## Rendering Nothing + +To render nothing, you can return `None` from a component. This is useful if you want to conditionally hide something: + +\```rust, no_run +{{#include src/doc_examples/conditional_rendering.rs:conditional_none}} +\``` + +\```inject-dioxus +DemoFrame { + conditional_rendering::LogInWarningApp {} +} +\``` + +This works because the `Element` type is just an alias for `Option` + +> Again, you may use a different method to conditionally return `None`. For example the boolean's [`then()`](https://doc.rust-lang.org/std/primitive.bool.html#method.then) function could be used. + +## Rendering Lists + +Often, you'll want to render a collection of components. For example, you might want to render a list of all comments on a post. + +For this, Dioxus accepts iterators that produce `Element`s. So we need to: + +- Get an iterator over all of our items (e.g., if you have a `Vec` of comments, iterate over it with `iter()`) +- `.map` the iterator to convert each item into a `LazyNode` using `rsx!{...}` + - Add a unique `key` attribute to each iterator item +- Include this iterator in the final RSX (or use it inline) + +Example: suppose you have a list of comments you want to render. Then, you can render them like this: + +\```rust, no_run +{{#include src/doc_examples/rendering_lists.rs:render_list}} +\``` +\```inject-dioxus +DemoFrame { + rendering_lists::App {} +} +\``` + +### Inline for loops + +Because of how common it is to render a list of items, Dioxus provides a shorthand for this. Instead of using `.iter`, `.map`, and `rsx`, you can use a `for` loop with a body of rsx code: + +\```rust, no_run +{{#include src/doc_examples/rendering_lists.rs:render_list_for_loop}} +\``` +\```inject-dioxus +DemoFrame { + rendering_lists::AppForLoop {} +} +\``` + +### The `key` Attribute + +Every time you re-render your list, Dioxus needs to keep track of which items go where to determine what updates need to be made to the UI. + +For example, suppose the `CommentComponent` had some state – e.g. a field where the user typed in a reply. If the order of comments suddenly changes, Dioxus needs to correctly associate that state with the same comment – otherwise, the user will end up replying to a different comment! + +To help Dioxus keep track of list items, we need to associate each item with a unique key. In the example above, we dynamically generated the unique key. In real applications, it's more likely that the key will come from e.g. a database ID. It doesn't matter where you get the key from, as long as it meets the requirements: + +- Keys must be unique in a list +- The same item should always get associated with the same key +- Keys should be relatively small (i.e. converting the entire Comment structure to a String would be a pretty bad key) so they can be compared efficiently + +You might be tempted to use an item's index in the list as its key. That’s what Dioxus will use if you don’t specify a key at all. This is only acceptable if you can guarantee that the list is constant – i.e., no re-ordering, additions, or deletions. + +> Note that if you pass the key to a component you've made, it won't receive the key as a prop. It’s only used as a hint by Dioxus itself. If your component needs an ID, you have to pass it as a separate prop. diff --git a/docs-src/0.5/zh/reference/event_handlers.md b/docs-src/0.5/zh/reference/event_handlers.md new file mode 100644 index 000000000..6b5b90093 --- /dev/null +++ b/docs-src/0.5/zh/reference/event_handlers.md @@ -0,0 +1,87 @@ +# Event Handlers + +Event handlers are used to respond to user actions. For example, an event handler could be triggered when the user clicks, scrolls, moves the mouse, or types a character. + +Event handlers are attached to elements. For example, we usually don't care about all the clicks that happen within an app, only those on a particular button. + +Event handlers are similar to regular attributes, but their name usually starts with `on`- and they accept closures as values. The closure will be called whenever the event it listens for is triggered and will be passed that event. + +For example, to handle clicks on an element, we can specify an `onclick` handler: + +\```rust, no_run +{{#include src/doc_examples/event_click.rs:rsx}} +\``` +\```inject-dioxus +DemoFrame { + event_click::App {} +} +\``` + +## The `Event` object + +Event handlers receive an [`Event`](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Event.html) object containing information about the event. Different types of events contain different types of data. For example, mouse-related events contain [`MouseData`](https://docs.rs/dioxus/latest/dioxus/events/struct.MouseData.html), which tells you things like where the mouse was clicked and what mouse buttons were used. + +In the example above, this event data was logged to the terminal: + +\``` +Clicked! Event: UiEvent { bubble_state: Cell { value: true }, data: MouseData { coordinates: Coordinates { screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } } +Clicked! Event: UiEvent { bubble_state: Cell { value: true }, data: MouseData { coordinates: Coordinates { screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } } +\``` + +To learn what the different event types for HTML provide, read the [events module docs](https://docs.rs/dioxus-html/latest/dioxus_html/events/index.html). + +### Event propagation + +Some events will trigger first on the element the event originated at upward. For example, a click event on a `button` inside a `div` would first trigger the button's event listener and then the div's event listener. + +> For more information about event propagation see [the mdn docs on event bubbling](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling) + +If you want to prevent this behavior, you can call `stop_propagation()` on the event: + +\```rust, no_run +{{#include src/doc_examples/event_nested.rs:rsx}} +\``` + +## Prevent Default + +Some events have a default behavior. For keyboard events, this might be entering the typed character. For mouse events, this might be selecting some text. + +In some instances, might want to avoid this default behavior. For this, you can add the `prevent_default` attribute with the name of the handler whose default behavior you want to stop. This attribute can be used for multiple handlers using their name separated by spaces: + +\```rust, no_run +{{#include src/doc_examples/event_prevent_default.rs:prevent_default}} +\``` + +\```inject-dioxus +DemoFrame { + event_prevent_default::App {} +} +\``` + +Any event handlers will still be called. + +> Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does _not_ currently support this behavior. Note: this means you cannot conditionally prevent default behavior based on the data in the event. + +## Handler Props + +Sometimes, you might want to make a component that accepts an event handler. A simple example would be a `FancyButton` component, which accepts an `on_click` handler: + +\```rust, no_run +{{#include src/doc_examples/event_handler_prop.rs:component_with_handler}} +\``` + +Then, you can use it like any other handler: + +\```rust, no_run +{{#include src/doc_examples/event_handler_prop.rs:usage}} +\``` + +> Note: just like any other attribute, you can name the handlers anything you want! Though they must start with `on`, for the prop to be automatically turned into an `EventHandler` at the call site. + +## Custom Data + +Event Handlers are generic over any type, so you can pass in any data you want to them, e.g: + +\```rust, no_run +{{#include src/doc_examples/event_handler_prop.rs:custom_data}} +\``` \ No newline at end of file diff --git a/docs-src/0.5/zh/reference/fullstack/authentication.md b/docs-src/0.5/zh/reference/fullstack/authentication.md new file mode 100644 index 000000000..4f5d4bf39 --- /dev/null +++ b/docs-src/0.5/zh/reference/fullstack/authentication.md @@ -0,0 +1,7 @@ +# Authentication + +You can use [extractors](./extractors) to integrate auth with your Fullstack application. + +You can create a custom extractors that extracts the auth session from the request. From that auth session, you can check if the user has the required privileges before returning the private data. + +A [full auth example](https://github.com/dioxuslabs/dioxus/blob/main/packages/fullstack/examples/axum-auth/src/main.rs) with the complete implementation is available in the fullstack examples. diff --git a/docs-src/0.5/zh/reference/fullstack/extractors.md b/docs-src/0.5/zh/reference/fullstack/extractors.md new file mode 100644 index 000000000..2d3eee81f --- /dev/null +++ b/docs-src/0.5/zh/reference/fullstack/extractors.md @@ -0,0 +1,11 @@ +# Extractors + +Server functions are an ergonomic way to call a function on the server. Server function work by registering an endpoint on the server and using requests on the client. Most of the time, you shouldn't need to worry about how server functions operate, but there are some times when you need to get some value from the request other than the data passed in the server function. + +For example, requests contain information about the user's browser (called the [user agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent)). We can use an extractor to retrieve that information. + +You can use the `extract` method within a server function to extract something from the request. You can extract any type that implements `FromServerContext` (or when axum is enabled, you can use axum extractors directly): + +\```rust +{{#include src/doc_examples/server_function_extract.rs:server_function_extract}} +\``` diff --git a/docs-src/0.5/zh/reference/fullstack/index.md b/docs-src/0.5/zh/reference/fullstack/index.md new file mode 100644 index 000000000..786e75b54 --- /dev/null +++ b/docs-src/0.5/zh/reference/fullstack/index.md @@ -0,0 +1,11 @@ +# Fullstack development + +Dioxus Fullstack contains helpers for: +- Incremental, static, and server side rendering +- Hydrating your application on the Client +- Communicating between a server and a client + +This guide will teach you everything you need to know about how to use the utilities in Dioxus fullstack to create amazing fullstack applications. + + +> In addition to this guide, you can find more examples of full-stack apps and information about how to integrate with other frameworks and desktop renderers in the [dioxus-fullstack examples directory](https://github.com/DioxusLabs/dioxus/tree/main/packages/fullstack/examples). diff --git a/docs-src/0.5/zh/reference/fullstack/middleware.md b/docs-src/0.5/zh/reference/fullstack/middleware.md new file mode 100644 index 000000000..8365c88f3 --- /dev/null +++ b/docs-src/0.5/zh/reference/fullstack/middleware.md @@ -0,0 +1,9 @@ +# Middleware + +Extractors allow you to wrap your server function in some code that changes either the request or the response. Dioxus fullstack integrates with [Tower](https://docs.rs/tower/latest/tower/index.html) to allow you to wrap your server functions in middleware. + +You can use the `#[middleware]` attribute to add a layer of middleware to your server function. Let's add a timeout middleware to a server function. This middleware will stop running the server function if it reaches a certain timeout: + +\```rust +{{#include src/doc_examples/server_function_middleware.rs:server_function_middleware}} +\``` diff --git a/docs-src/0.5/zh/reference/fullstack/publishing.md b/docs-src/0.5/zh/reference/fullstack/publishing.md new file mode 100644 index 000000000..897db740b --- /dev/null +++ b/docs-src/0.5/zh/reference/fullstack/publishing.md @@ -0,0 +1,31 @@ +## Publishing with Github Pages + +You can use Dioxus fullstack to pre-render your application and then hydrate it on the client. This works well with pages hosted statically on providers like Github Pages. In fact, the official Dioxus site uses this approach. + +You can set up your application to statically generate all static pages: + +\```rust +{{#include src/doc_examples/fullstack_static.rs}} +\``` + +Next, edit your `Dioxus.toml` to point your `out_dir` to the `docs` folder and the `base_path` to the name of your repo: + +\```toml +[application] +# ... +out_dir = "docs" + +[web.app] +base_path = "your_repo" +\``` + +Then build your app and publish it to Github: + +- Make sure GitHub Pages is set up for your repo to publish any static files in the docs directory +- Build your app with: +\```sh +dx build --release --features web +cargo run --features ssr +\``` +- Add and commit with git +- Push to GitHub diff --git a/docs-src/0.5/zh/reference/fullstack/routing.md b/docs-src/0.5/zh/reference/fullstack/routing.md new file mode 100644 index 000000000..3bcee654e --- /dev/null +++ b/docs-src/0.5/zh/reference/fullstack/routing.md @@ -0,0 +1,13 @@ +# Routing + +You can easily integrate your fullstack application with a client side router using Dioxus Router. This allows you to create different scenes in your app and navigate between them. You can read more about the router in the [router reference](../router.md) + +\```rust +{{#include src/doc_examples/server_router.rs}} +\``` + +\```inject-dioxus +SandBoxFrame { + url: "https://codesandbox.io/p/sandbox/dioxus-fullstack-router-s75v5q?file=%2Fsrc%2Fmain.rs%3A7%2C1" +} +\``` diff --git a/docs-src/0.5/zh/reference/fullstack/server_functions.md b/docs-src/0.5/zh/reference/fullstack/server_functions.md new file mode 100644 index 000000000..541f0b7e5 --- /dev/null +++ b/docs-src/0.5/zh/reference/fullstack/server_functions.md @@ -0,0 +1,108 @@ +# Communicating with the server + +`dioxus-fullstack` provides server functions that allow you to call an automatically generated API on the server from the client as if it were a local function. + +To make a server function, simply add the `#[server(YourUniqueType)]` attribute to a function. The function must: + +- Be an async function +- Have arguments and a return type that both implement serialize and deserialize (with [serde](https://serde.rs/)). +- Return a `Result` with an error type of ServerFnError + +You must call `register` on the type you passed into the server macro in your main function before starting your server to tell Dioxus about the server function. + +Let's continue building on the app we made in the [getting started](../../getting_started/fullstack.md) guide. We will add a server function to our app that allows us to double the count on the server. + +First, add serde as a dependency: + +\```shell +cargo add serde +\``` + +Next, add the server function to your `main.rs`: + +\```rust +{{#include src/doc_examples/server_function.rs}} +\``` + +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. + +## Cached data fetching + +One common use case for server functions is fetching data from the server: + +\```rust +{{#include src/doc_examples/server_data_fetch.rs}} +\``` + +If you navigate to the site above, you will first see `server data is None`, then after the `WASM` has loaded and the request to the server has finished, you will see `server data is Some(Ok("Hello from the server!"))`. + + +This approach works, but it can be slow. Instead of waiting for the client to load and send a request to the server, what if we could get all of the data we needed for the page on the server and send it down to the client with the initial HTML page? + + +This is exactly what the `use_server_future` hook allows us to do! `use_server_future` is similar to the `use_resource` hook, but it allows you to wait for a future on the server and send the result of the future down to the client. + + +Let's change our data fetching to use `use_server_future`: + +\```rust +{{#include src/doc_examples/server_data_prefetch.rs}} +\``` + +> Notice the `?` after `use_server_future`. This is what tells Dioxus fullstack to wait for the future to resolve before continuing rendering. If you want to not wait for a specific future, you can just remove the ? and deal with the `Option` manually. + +Now when you load the page, you should see `server data is Ok("Hello from the server!")`. No need to wait for the `WASM` to load or wait for the request to finish! + +\```inject-dioxus +SandBoxFrame { + url: "https://codesandbox.io/p/sandbox/dioxus-fullstack-server-future-qwpp4p?file=/src/main.rs:3,24" +} +\``` + + +## Running the client with dioxus-desktop + +The project presented so far makes a web browser interact with the server, but it is also possible to make a desktop program interact with the server in a similar fashion. (The full example code is available in the [Dioxus repo](https://github.com/DioxusLabs/dioxus/tree/main/packages/fullstack/examples/axum-desktop)) + +First, we need to make two binary targets, one for the desktop program (the `client.rs` file), one for the server (the `server.rs` file). The client app and the server functions are written in a shared `lib.rs` file. + +The desktop and server targets have slightly different build configuration to enable additional dependencies or features. +The Cargo.toml in the full example has more information, but the main points are: +- the client.rs has to be run with the `desktop` feature, so that the optional `dioxus-desktop` dependency is included +- the server.rs has to be run with the `ssr` features; this will generate the server part of the server functions and will run our backend server. + +Once you create your project, you can run the server executable with: +\```bash +cargo run --bin server --features ssr +\``` +and the client desktop executable with: +\```bash +cargo run --bin client --features desktop +\``` + +### Client code + +The client file is pretty straightforward. You only need to set the server url in the client code, so it knows where to send the network requests. Then, dioxus_desktop launches the app. + +For development, the example project runs the server on `localhost:8080`. **Before you release remember to update the url to your production url.** + + +### Server code + +In the server code, first you have to set the network address and port where the server will listen to. +\```rust +{{#include src/doc_examples/server_function_desktop_client.rs:server_url}} +\``` + +Then, you have to register the types declared in the server function macros into the server. +For example, consider this server function: +\```rust +{{#include src/doc_examples/server_function_desktop_client.rs:server_function}} +\``` + +The `GetServerData` type has to be registered in the server, which will add the corresponding route to the server. +\```rust +{{#include src/doc_examples/server_function_desktop_client.rs:function_registration}} +\``` + +Finally, the server is started and it begins responding to requests. diff --git a/docs-src/0.5/zh/reference/hooks.md b/docs-src/0.5/zh/reference/hooks.md new file mode 100644 index 000000000..7e981c070 --- /dev/null +++ b/docs-src/0.5/zh/reference/hooks.md @@ -0,0 +1,99 @@ +# Hooks and component state + +So far, our components have had no state like a normal Rust function. 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 you call in a constant order in a component that add additional functionality to the component. + +Dioxus provides many built-in hooks, but if those hooks don't fit your specific use case, you also can [create your own hook](../cookbook/state/custom_hooks/index.md) + +## use_signal hook + +[`use_signal`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_signal.html) is one of the simplest hooks. + +- You provide a closure that determines the initial value: `let mut count = use_signal(|| 0);` +- `use_signal` gives you the current value, and a way to write to the value +- When the value updates, `use_signal` 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_signal` hook: + +\```rust, no_run +{{#include src/doc_examples/hooks_counter.rs:component}} +\``` +\```inject-dioxus +DemoFrame { + hooks_counter::App {} +} +\``` + +Every time the component's state changes, it re-renders, and the component function is called, so you can describe what you want the new UI to look like. You don't have to worry about "changing" anything – describe what you want in terms of the state, and Dioxus will take care of the rest! + +> `use_signal` returns your value wrapped in a smart pointer of type [`Signal`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Signal.html) that is `Copy`. This is why you can both read the value and update it, even within an event handler. + +You can use multiple hooks in the same component if you want: + +\```rust, no_run +{{#include src/doc_examples/hooks_counter_two_state.rs:component}} +\``` + +\```inject-dioxus +DemoFrame { + hooks_counter_two_state::App {} +} +\``` + +You can also use `use_signal` to store more complex state, like a Vec. You can read and write to the state with the `read` and `write` methods: + +\```rust, no_run +{{#include src/doc_examples/hooks_use_signal.rs:component}} +\``` + +\```inject-dioxus +DemoFrame { + hooks_use_signal::App {} +} +\``` + +## Rules of hooks + +The above example might seem a bit magic since Rust functions are typically not associated with state. Dioxus allows hooks to maintain state across renders through a hidden scope that is associated with the component. + +But how can Dioxus differentiate between multiple hooks in the same component? As you saw in the second example, both `use_signal` functions were called with the same parameters, so how come they can return different things when the counters are different? + +\```rust, no_run +{{#include src/doc_examples/hooks_counter_two_state.rs:use_signal_calls}} +\``` + +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). +2. On every call to a component function. +3. The same hooks must be called (except in the case of early returns, as explained later in the [Error Handling chapter](../../cookbook/error_handling.md)). +4. In the same order. +5. Hook names should start with `use_` so you don't accidentally confuse them with regular + functions (`use_signal()`, `use_signal()`, `use_resource()`, etc...). + +These rules mean that there are certain things you can't do with hooks: + +### No hooks in conditionals + +\```rust, no_run +{{#include src/doc_examples/hooks_bad.rs:conditional}} +\``` + +### No hooks in closures + +\```rust, no_run +{{#include src/doc_examples/hooks_bad.rs:closure}} +\``` + +### No hooks in loops + +\```rust, no_run +{{#include src/doc_examples/hooks_bad.rs:loop}} +\``` + +## Additional resources + +- [dioxus_hooks API docs](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/) +- [dioxus_hooks source code](https://github.com/DioxusLabs/dioxus/tree/main/packages/hooks) diff --git a/docs-src/0.5/zh/reference/index.md b/docs-src/0.5/zh/reference/index.md new file mode 100644 index 000000000..c5141a3e1 --- /dev/null +++ b/docs-src/0.5/zh/reference/index.md @@ -0,0 +1,36 @@ +# Dioxus Reference + +This Reference contains more detailed explanations for all concepts covered in the [guide](../guide/index.md) and more. + +## Rendering + +- [RSX](rsx.md): Rsx is a HTML-like macro that allows you to declare UI +- [Components](components.md): Components are the building blocks of UI in Dioxus +- [Props](component_props.md): Props allow you pass information to Components +- [Event Listeners](event_handlers.md): Event listeners let you respond to user input +- [User Input](user_input.md): How to handle User input in Dioxus +- [Dynamic Rendering](dynamic_rendering.md): How to dynamically render data in Dioxus + +## State + +- [Hooks](hooks.md): Hooks allow you to create components state +- [Context](context.md): Context allows you to create state in a parent and consume it in children +- [Routing](router.md): The router helps you manage the URL state +- [Resource](use_resource.md): Use future allows you to create an async task and monitor it's state +- [UseCoroutine](use_coroutine.md): Use coroutine helps you manage external state +- [Spawn](spawn.md): Spawn creates an async task + +## Platforms + +- [Choosing a Web Renderer](choosing_a_web_renderer.md): Overview of the different web renderers +- [Desktop](desktop/index.md): Overview of desktop specific APIS +- [Web](web/index.md): Overview of web specific APIS +- [Fullstack](fullstack/index.md): Overview of Fullstack specific APIS + - [Server Functions](fullstack/server_functions.md): Server functions make it easy to communicate between your server and client + - [Extractors](fullstack/extractors.md): Extractors allow you to get extra information out of the headers of a request + - [Middleware](fullstack/middleware.md): Middleware allows you to wrap a server function request or response + - [Authentication](fullstack/authentication.md): An overview of how to handle authentication with server functions + - [Routing](fullstack/routing.md): An overview of how to work with the router in the fullstack renderer +- [SSR](ssr.md): Overview of the SSR renderer +- [TUI](tui.md): Overview of web specific APIS +- [Liveview](liveview.md): Overview of liveview specific APIS diff --git a/docs-src/0.5/zh/reference/liveview.md b/docs-src/0.5/zh/reference/liveview.md new file mode 100644 index 000000000..beaecf019 --- /dev/null +++ b/docs-src/0.5/zh/reference/liveview.md @@ -0,0 +1,36 @@ +# Liveview + +Liveview allows apps to *run* on the server and *render* in the browser. It uses WebSockets to communicate between the server and the browser. + +Examples: +- [Simple Example](https://github.com/DioxusLabs/dioxus/tree/main/packages/liveview/examples/axum.rs) + +## Support + +Dioxus liveview will be migrated to [dioxus-fullstack](./fullstack/index.md) in a future release. Once this migration occurs, you may need to update your code. We plan for this migration to be minimal. + +Liveview is currently limited in capability when compared to the Web platform. Liveview apps run on the server in a native thread. This means that browser APIs are not available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs are accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. + +## Router Integration + +Currently, the Dioxus router does not integrate with the browser history in the liveview renderer. If you are interested in contributing this feature to Dioxus this issue is tracked [here](https://github.com/DioxusLabs/dioxus/issues/1038). + +## Managing Latency + +Liveview makes it incredibly convenient to talk to your server from the client, but there are some downsides. Mainly in Dioxus Liveview every interaction goes through the server by default. + + +Because of this, with the liveview renderer you need to be very deliberate about managing latency. Events that would be fast enough on other renderers like [controlled inputs](../../reference/user_input.md), can be frustrating to use in the liveview renderer. + + +To get around this issue you can inject bits of javascript in your liveview application. If you use a raw attribute as a listener, you can inject some javascript that will be run when the event is triggered: + +\```rust +rsx! { + div { + input { + "oninput": "console.log('input changed!')" + } + } +} +\``` diff --git a/docs-src/0.5/zh/reference/mobile/apis.md b/docs-src/0.5/zh/reference/mobile/apis.md new file mode 100644 index 000000000..27b5aa617 --- /dev/null +++ b/docs-src/0.5/zh/reference/mobile/apis.md @@ -0,0 +1,26 @@ +# Mobile + +This guide will cover concepts specific to the Dioxus mobile renderer. + +## Running Javascript + +Dioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose. + + +For these cases, Dioxus desktop exposes the use_eval hook that allows you to run raw Javascript in the webview: + +\```rust +{{#include src/doc_examples/eval.rs}} +\``` + +## Custom Assets + +You can link to local assets in dioxus mobile instead of using a url: + +\```rust +{{#include src/doc_examples/custom_assets.rs}} +\``` + +## Integrating with Wry + +In cases where you need more low level control over your window, you can use wry APIs exposed through the [Desktop Config](https://docs.rs/dioxus-desktop/0.5.0/dioxus_desktop/struct.Config.html) and the [use_window hook](https://docs.rs/dioxus-desktop/0.5.0/dioxus_desktop/struct.DesktopContext.html) diff --git a/docs-src/0.5/zh/reference/mobile/index.md b/docs-src/0.5/zh/reference/mobile/index.md new file mode 100644 index 000000000..5f8c8032f --- /dev/null +++ b/docs-src/0.5/zh/reference/mobile/index.md @@ -0,0 +1,269 @@ +# Mobile App + +Build a mobile app with Dioxus! + +Example: [Mobile Demo](https://github.com/DioxusLabs/dioxus/tree/main/examples/mobile_demo) + +## Support + +Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with either the platform's WebView or experimentally with [WGPU](https://github.com/DioxusLabs/blitz). WebView doesn't support animations, transparency, and native widgets. + + +Mobile support is currently best suited for CRUD-style apps, ideally for internal teams who need to develop quickly but don't care much about animations or native widgets. + +## Getting Set up + +Getting set up with mobile can be quite challenging. The tooling here isn't great (yet) and might take some hacking around to get things working. + +### Setting up dependencies + +#### Android + +First, install the rust Android targets: + +\```sh +rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android +\``` + +To develop on Android, you will need to [install Android Studio](https://developer.android.com/studio). + +Once you have installed Android Studio, you will need to install the Android SDK and NDK: + +1. Create a blank Android project +2. Select `Tools > SDK manager` +3. Navigate to the `SDK tools` window: + +![NDK install window](./public/static/android_ndk_install.png) + +Then select: +- The SDK +- The SDK Command line tools +- The NDK (side by side) +- CMAKE + +4. Select `apply` and follow the prompts + +> More details that could be useful for debugging any errors you encounter are available [in the official android docs](https://developer.android.com/studio/intro/update#sdk-manager) + +Next set the Java, Android and NDK home variables: + +Mac: +\```sh +export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" +export ANDROID_HOME="$HOME/Library/Android/sdk" +export NDK_HOME="$ANDROID_HOME/ndk/25.2.9519653" +\``` + +Windows: +\```powershell +[System.Environment]::SetEnvironmentVariable("JAVA_HOME", "C:\Program Files\Android\Android Studio\jbr", "User") +[System.Environment]::SetEnvironmentVariable("ANDROID_HOME", "$env:LocalAppData\Android\Sdk", "User") +[System.Environment]::SetEnvironmentVariable("NDK_HOME", "$env:LocalAppData\Android\Sdk\ndk\25.2.9519653", "User") +\``` + +> The NDK version in the paths should match the version you installed in the last step + +#### IOS + +First, install the rust IOS targets: + +\```sh +rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim +\``` + +To develop on IOS, you will need to [install XCode](https://apps.apple.com/us/app/xcode/id497799835). + +> Note: On Apple silicon you must run Xcode on rosetta. Goto Application > Right Click Xcode > Get Info > Open in Rosetta. +> If you are using M1, you will have to run `cargo build --target x86_64-apple-ios` instead of `cargo apple build` if you want to run in simulator. + +### Setting up your project + +First, we need to create a rust project: + +\```sh +cargo new dioxus-mobile-test --lib +cd dioxus-mobile-test +\``` + +Next, we can use `cargo-mobile2` to create a project for mobile: + +\```shell +cargo install --git https://github.com/tauri-apps/cargo-mobile2 +cargo mobile init +\``` + +When you run `cargo mobile init`, you will be asked a series of questions about your project. One of those questions is what template you should use. Dioxus currently doesn't have a template in Tauri mobile, instead you can use the `wry` template. + +> You may also be asked to input your team ID for IOS. You can find your team id [here](https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/) or create a team id by creating a developer account [here](https://developer.apple.com/help/account/get-started/about-your-developer-account) + +Next, we need to modify our dependencies to include dioxus and ensure the right version of wry is installed. Change the `[dependencies]` section of your `Cargo.toml`: + +\```toml +[dependencies] +anyhow = "1.0.56" +log = "0.4.11" +dioxus = { version = "0.5", features = ["mobile"] } +wry = "0.35.0" +tao = "0.25.0" +\``` + +Finally, we need to add a component to renderer. Replace the wry template in your `lib.rs` file with this code: + +\```rust +use anyhow::Result; +use dioxus::prelude::*; + +#[cfg(target_os = "android")] +fn init_logging() { + android_logger::init_once( + android_logger::Config::default() + .with_max_level(log::LevelFilter::Trace) + ); +} + +#[cfg(not(target_os = "android"))] +fn init_logging() { + env_logger::init(); +} + +#[cfg(any(target_os = "android", target_os = "ios"))] +fn stop_unwind T, T>(f: F) -> T { + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) { + Ok(t) => t, + Err(err) => { + eprintln!("attempt to unwind out of `rust` with err: {:?}", err); + std::process::abort() + } + } +} + +#[no_mangle] +#[inline(never)] +#[cfg(any(target_os = "android", target_os = "ios"))] +pub extern "C" fn start_app() { + fn _start_app() { + stop_unwind(|| main().unwrap()); + } + + #[cfg(target_os = "android")] + { + tao::android_binding!( + com_example, + dioxus_mobile_test, + WryActivity, + wry::android_setup, // pass the wry::android_setup function to tao which will invoke when the event loop is created + _start_app + ); + wry::android_binding!(com_example, dioxus_mobile_test); + } + #[cfg(target_os = "ios")] + _start_app() +} + +pub fn main() -> Result<()> { + init_logging(); + + launch(app); + + Ok(()) +} + +fn app() -> Element { + let mut items = use_signal(|| vec![1, 2, 3]); + + log::debug!("Hello from the app"); + + rsx! { + div { + h1 { "Hello, Mobile"} + div { margin_left: "auto", margin_right: "auto", width: "200px", padding: "10px", border: "1px solid black", + button { + onclick: move|_| { + println!("Clicked!"); + let mut items_mut = items.write(); + let new_item = items_mut.len() + 1; + items_mut.push(new_item); + println!("Requested update"); + }, + "Add item" + } + for item in items.read().iter() { + div { "- {item}" } + } + } + } + } +} +\``` + +## Running + +From there, you'll want to get a build of the crate using whichever platform you're targeting (simulator or actual hardware). For now, we'll just stick with the simulator. + +First, you need to make sure that the build variant is correct in Android Studio: +1. Click "Build" in the top menu bar. +2. Click "Select Build Variant..." in the dropdown. +3. Find the "Build Variants" panel and use the dropdown to change the selected build variant. + +![android studio build dropdown](./public/static/as-build-dropdown.png) +![android studio build variants](./public/static/as-build-variant-menu.png) + +### Android + +To build your project on Android you can run: + +\```sh +cargo android build +\``` + +Next, open Android studio: +\```sh +cargo android open +\``` + +This will open an android studio project for this application. + +Next we need to create a simulator in Android studio to run our app in. To create a simulator click on the phone icon in the top right of Android studio: + +![android studio manage devices](./public/static/android-studio-simulator.png) + +Then click the `create a virtual device` button and follow the prompts: + +![android studio devices](./public/static/android-studio-devices.png) + +Finally, launch your device by clicking the play button on the device you created: + +![android studio device](./public/static/android-studio-device.png) + +Now you can start your application from your terminal by running: + +\```sh +cargo android run +\``` + +![android_demo](./public/static/Android-Dioxus-demo.png) + +> More information is available in the Android docs: +> - https://developer.android.com/ndk/guides +> - https://developer.android.com/studio/projects/install-ndk +> - https://source.android.com/docs/setup/build/rust/building-rust-modules/overview + +### IOS + +To build your project for IOS, you can run: +\```sh +cargo build --target aarch64-apple-ios-sim +\``` + +Next, open XCode (this might take awhile if you've never opened XCode before): +\```sh +cargo apple open +\``` + +This will open XCode with this particular project. + +From there, just click the "play" button with the right target and the app should be running! + +![ios_demo](./public/static/IOS-dioxus-demo.png) + +Note that clicking play doesn't cause a new build, so you'll need to keep rebuilding the app between changes. The tooling here is very young, so please be patient. If you want to contribute to make things easier, please do! We'll be happy to help. diff --git a/docs-src/0.5/zh/reference/router.md b/docs-src/0.5/zh/reference/router.md new file mode 100644 index 000000000..1eef4c16b --- /dev/null +++ b/docs-src/0.5/zh/reference/router.md @@ -0,0 +1,58 @@ +# Router + +In many of your apps, you'll want to have different "scenes". For a webpage, these scenes might be the different webpages with their own content. For a desktop app, these scenes might be different views in your app. + +To unify these platforms, Dioxus provides a first-party solution for scene management called Dioxus Router. + + +## What is it? + +For an app like the Dioxus landing page (https://dioxuslabs.com), we want to have several different scenes: + +- Homepage +- Blog + +Each of these scenes is independent – we don't want to render both the homepage and blog at the same time. + +The Dioxus router makes it easy to create these scenes. To make sure we're using the router, add the `router` feature to your `dioxus` dependency: + +\```shell +cargo add dioxus@0.5.0 --features router +\``` + + +## Using the router + +Unlike other routers in the Rust ecosystem, our router is built declaratively at compile time. This makes it possible to compose our app layout simply by defining an enum. + +\```rust +{{#include src/doc_examples/router_reference.rs:router_definition}} +\``` + +Whenever we visit this app, we will get either the Home component or the Blog component rendered depending on which route we enter at. If neither of these routes match the current location, then nothing will render. + +We can fix this one of two ways: + +- A fallback 404 page + +\```rust +{{#include src/doc_examples/router_reference.rs:router_definition_404}} +\``` + +- Redirect 404 to home + +\```rust +{{#include src/doc_examples/router_reference.rs:router_404_redirect}} +\``` + +## Links + +For our app to navigate these routes, we can provide clickable elements called Links. These simply wrap `` elements that, when clicked, navigate the app to the given location. Because our route is an enum of valid routes, if you try to link to a page that doesn't exist, you will get a compiler error. + +\```rust +{{#include src/doc_examples/router_reference.rs:links}} +\``` + +## More reading + +This page is just a very brief overview of the router. For more information, check out the [router book](../router/index.md) or some of the [router examples](https://github.com/DioxusLabs/dioxus/blob/master/examples/router.rs). diff --git a/docs-src/0.5/zh/reference/rsx.md b/docs-src/0.5/zh/reference/rsx.md new file mode 100644 index 000000000..f8cb1f22a --- /dev/null +++ b/docs-src/0.5/zh/reference/rsx.md @@ -0,0 +1,230 @@ +# Describing the UI + +Dioxus is a _declarative_ framework. This means that instead of telling Dioxus what to do (e.g. to "create an element" or "set the color to red") we simply _declare_ what we want the UI to look like using RSX. + +You have already seen a simple example of RSX syntax in the "hello world" application: + +\```rust, no_run +{{#include src/doc_examples/hello_world_desktop.rs:component}} +\``` + +Here, we use the `rsx!` macro to _declare_ that we want a `div` element, containing the text `"Hello, world!"`. Dioxus takes the RSX and constructs a UI from it. + +## RSX Features + +RSX is very similar to HTML in that it describes elements with attributes and children. Here's an empty `button` element in RSX, as well as the resulting HTML: + +\```rust, no_run +{{#include src/doc_examples/rsx_overview.rs:button}} +\``` + +\```inject-dioxus +DemoFrame { + rsx_overview::Button {} +} +\``` + +### Attributes + +Attributes (and [event handlers](event_handlers.md)) modify the behavior or appearance of the element they are attached to. They are specified inside the `{}` brackets, using the `name: value` syntax. You can provide the value as a literal in the RSX: + +\```rust, no_run +{{#include src/doc_examples/rsx_overview.rs:attributes}} +\``` + +\```inject-dioxus +DemoFrame { + rsx_overview::Attributes {} +} +\``` + +Some attributes, such as the ``type`` attribute for ``input`` elements won't work on their own in Rust. This is because ``type`` is a reserved Rust keyword. To get around this, Dioxus uses the ``r#`` specifier: +\```rust, no_run +{{#include src/doc_examples/rsx_overview.rs:attributes_type}} +\``` + +> Note: All attributes defined in `dioxus-html` follow the snake_case naming convention. They transform their `snake_case` names to HTML's `camelCase` attributes. + +> Note: Styles can be used directly outside of the `style:` attribute. In the above example, `color: "red"` is turned into `style="color: red"`. + +#### Conditional Attributes + +You can also conditionally include attributes by using an if statement without an else branch. This is useful for adding an attribute only if a certain condition is met: + +\```rust, no_run +{{#include src/doc_examples/rsx_overview.rs:conditional_attributes}} +\``` + +\```inject-dioxus +DemoFrame { + rsx_overview::ConditionalAttributes {} +} +\``` + +#### Custom Attributes + +Dioxus has a pre-configured set of attributes that you can use. RSX is validated at compile time to make sure you didn't specify an invalid attribute. If you want to override this behavior with a custom attribute name, specify the attribute in quotes: + +\```rust, no_run +{{#include src/doc_examples/rsx_overview.rs:custom_attributes}} +\``` + +\```inject-dioxus +DemoFrame { + rsx_overview::CustomAttributes {} +} +\``` + +### Special Attributes + +While most attributes are simply passed on to the HTML, some have special behaviors. + +#### The HTML Escape Hatch + +If you're working with pre-rendered assets, output from templates, or output from a JS library, then you might want to pass HTML directly instead of going through Dioxus. In these instances, reach for `dangerous_inner_html`. + +For example, shipping a markdown-to-Dioxus converter might significantly bloat your final application size. Instead, you'll want to pre-render your markdown to HTML and then include the HTML directly in your output. We use this approach for the [Dioxus homepage](https://dioxuslabs.com): + +\```rust, no_run +{{#include src/doc_examples/dangerous_inner_html.rs:dangerous_inner_html}} +\``` + +\```inject-dioxus +DemoFrame { + dangerous_inner_html::App {} +} +\``` +> Note! This attribute is called "dangerous_inner_html" because it is **dangerous** to pass it data you don't trust. If you're not careful, you can easily expose [cross-site scripting (XSS)](https://en.wikipedia.org/wiki/Cross-site_scripting) attacks to your users. +> +> If you're handling untrusted input, make sure to sanitize your HTML before passing it into `dangerous_inner_html` – or just pass it to a Text Element to escape any HTML tags. + +#### Boolean Attributes + +Most attributes, when rendered, will be rendered exactly as the input you provided. However, some attributes are considered "boolean" attributes and just their presence determines whether they affect the output. For these attributes, a provided value of `"false"` will cause them to be removed from the target element. + +So this RSX wouldn't actually render the `hidden` attribute: + +\```rust, no_run +{{#include src/doc_examples/boolean_attribute.rs:boolean_attribute}} +\``` + +\```inject-dioxus +DemoFrame { + boolean_attribute::App {} +} +\``` + +Not all attributes work like this however. _Only the following attributes_ have this behavior: + +- `allowfullscreen` +- `allowpaymentrequest` +- `async` +- `autofocus` +- `autoplay` +- `checked` +- `controls` +- `default` +- `defer` +- `disabled` +- `formnovalidate` +- `hidden` +- `ismap` +- `itemscope` +- `loop` +- `multiple` +- `muted` +- `nomodule` +- `novalidate` +- `open` +- `playsinline` +- `readonly` +- `required` +- `reversed` +- `selected` +- `truespeed` + +For any other attributes, a value of `"false"` will be sent directly to the DOM. + +### Interpolation + +Similarly to how you can [format](https://doc.rust-lang.org/rust-by-example/hello/print/fmt.html) Rust strings, you can also interpolate in RSX text. Use `{variable}` to Display the value of a variable in a string, or `{variable:?}` to use the Debug representation: + +\```rust, no_run +{{#include src/doc_examples/rsx_overview.rs:formatting}} +\``` + +\```inject-dioxus +DemoFrame { + rsx_overview::Formatting {} +} +\``` + +### Children + +To add children to an element, put them inside the `{}` brackets after all attributes and listeners in the element. They can be other elements, text, or [components](components.md). For example, you could have an `ol` (ordered list) element, containing 3 `li` (list item) elements, each of which contains some text: + +\```rust, no_run +{{#include src/doc_examples/rsx_overview.rs:children}} +\``` + +\```inject-dioxus +DemoFrame { + rsx_overview::Children {} +} +\``` + +### Fragments + +You can render multiple elements at the top level of `rsx!` and they will be automatically grouped. + +\```rust, no_run +{{#include src/doc_examples/rsx_overview.rs:manyroots}} +\``` + +\```inject-dioxus +DemoFrame { + rsx_overview::ManyRoots {} +} +\``` + +### Expressions + +You can include arbitrary Rust expressions as children within RSX by surrounding your expression with `{}`s. Any expression that implements [IntoDynNode](https://docs.rs/dioxus-core/0.3/dioxus_core/trait.IntoDynNode.html) can be used within rsx. This is useful for displaying data from an [iterator](https://doc.rust-lang.org/stable/book/ch13-02-iterators.html#processing-a-series-of-items-with-iterators): + +\```rust, no_run +{{#include src/doc_examples/rsx_overview.rs:expression}} +\``` + +\```inject-dioxus +DemoFrame { + rsx_overview::Expression {} +} +\``` + +### Loops + +In addition to iterators you can also use for loops directly within RSX: + +\```rust, no_run +{{#include src/doc_examples/rsx_overview.rs:loops}} +\``` + +\```inject-dioxus +DemoFrame { + rsx_overview::Loops {} +} +\``` + +### If statements + +You can also use if statements without an else branch within RSX: + +\```rust, no_run +{{#include src/doc_examples/rsx_overview.rs:ifstatements}} +\``` + +\```inject-dioxus +DemoFrame { + rsx_overview::IfStatements {} +} +\``` diff --git a/docs-src/0.5/zh/reference/spawn.md b/docs-src/0.5/zh/reference/spawn.md new file mode 100644 index 000000000..65ca4b855 --- /dev/null +++ b/docs-src/0.5/zh/reference/spawn.md @@ -0,0 +1,25 @@ +# Spawning Futures + +The `use_resource` and `use_coroutine` hooks are useful if you want to unconditionally spawn the future. Sometimes, though, you'll want to only spawn a future in response to an event, such as a mouse click. For example, suppose you need to send a request when the user clicks a "log in" button. For this, you can use `spawn`: + +\```rust +{{#include src/doc_examples/spawn.rs:spawn}} +\``` + +\```inject-dioxus +DemoFrame { + spawn::App {} +} +\``` + +> Note: `spawn` will always spawn a _new_ future. You most likely don't want to call it on every render. + +Calling `spawn` will give you a `JoinHandle` which lets you cancel or pause the future. + +## Spawning Tokio Tasks + +Sometimes, you might want to spawn a background task that needs multiple threads or talk to hardware that might block your app code. In these cases, we can directly spawn a Tokio task from our future. For Dioxus-Desktop, your task will be spawned onto Tokio's Multithreaded runtime: + +\```rust +{{#include src/doc_examples/spawn.rs:tokio}} +\``` diff --git a/docs-src/0.5/zh/reference/ssr.md b/docs-src/0.5/zh/reference/ssr.md new file mode 100644 index 000000000..187bdbc4f --- /dev/null +++ b/docs-src/0.5/zh/reference/ssr.md @@ -0,0 +1,62 @@ +# Server-Side Rendering + +For lower-level control over the rendering process, you can use the `dioxus-ssr` crate directly. This can be useful when integrating with a web framework that `dioxus-fullstack` does not support, or pre-rendering pages. + +## Setup + +For this guide, we're going to show how to use Dioxus SSR with [Axum](https://docs.rs/axum/latest/axum/). + +Make sure you have Rust and Cargo installed, and then create a new project: + +\```shell +cargo new --bin demo +cd demo +\``` + +Add Dioxus and the ssr renderer as dependencies: + +\```shell +cargo add dioxus@0.5.0 +cargo add dioxus-ssr@0.5.0 +\``` + +Next, add all the Axum dependencies. This will be different if you're using a different Web Framework + +\``` +cargo add tokio --features full +cargo add axum +\``` + +Your dependencies should look roughly like this: + +\```toml +[dependencies] +axum = "0.7" +dioxus = { version = "*" } +dioxus-ssr = { version = "*" } +tokio = { version = "1.15.0", features = ["full"] } +\``` + +Now, set up your Axum app to respond on an endpoint. + +\```rust +{{#include src/doc_examples/ssr.rs:main}} +\``` + +And then add our endpoint. We can either render `rsx!` directly: + +\```rust +{{#include src/doc_examples/ssr.rs:app_endpoint}} +\``` + +Or we can render VirtualDoms. + +\```rust +{{#include src/doc_examples/ssr.rs:app_endpoint_vdom}} +\``` + +## Multithreaded Support + +The Dioxus VirtualDom, sadly, is not currently `Send`. Internally, we use quite a bit of interior mutability which is not thread-safe. +When working with web frameworks that require `Send`, it is possible to render a VirtualDom immediately to a String – but you cannot hold the VirtualDom across an await point. For retained-state SSR (essentially LiveView), you'll need to spawn a VirtualDom on its own thread and communicate with it via channels or create a pool of VirtualDoms. +You might notice that you cannot hold the VirtualDom across an await point. Because Dioxus is currently not ThreadSafe, it _must_ remain on the thread it started. We are working on loosening this requirement. diff --git a/docs-src/0.5/zh/reference/tui.md b/docs-src/0.5/zh/reference/tui.md new file mode 100644 index 000000000..69fb7a215 --- /dev/null +++ b/docs-src/0.5/zh/reference/tui.md @@ -0,0 +1,19 @@ +# TUI + +You can build a text-based interface that will run in the terminal using Dioxus. + +![Hello World screenshot](https://github.com/DioxusLabs/rink/raw/master/examples/example.png) + +> Note: this book was written with HTML-based platforms in mind. You might be able to follow along with TUI, but you'll have to adapt a bit. + +## Support + +Development of dioxus-tui is currently paused while we focus on the backbone of dioxus' native render [Blitz](https://github.com/DioxusLabs/blitz). In the future, dioxus-tui may be updated to use the same backend as Blitz. If you are interested in contributing to dioxus-tui, pull requests are welcome. + +Dioxus TUI is currently quite experimental. But, if you're willing to venture into the realm of the unknown, you can use the `dioxus-tui` crate to build interactive apps with a limited subset of HTML. + +- It uses flexbox for the layout +- It only supports a subset of the attributes and elements +- Regular widgets will not work in the tui render, but the tui renderer has its own widget components that start with a capital letter. See the [widgets example](https://github.com/DioxusLabs/blitz/blob/master/packages/dioxus-tui/examples/widgets.rs) +- 1px is one character line height. Your regular CSS px does not translate +- If your app panics, your terminal is wrecked. This will be fixed eventually diff --git a/docs-src/0.5/zh/reference/use_coroutine.md b/docs-src/0.5/zh/reference/use_coroutine.md new file mode 100644 index 000000000..dc8d798fb --- /dev/null +++ b/docs-src/0.5/zh/reference/use_coroutine.md @@ -0,0 +1,71 @@ +# Coroutines + +Another tool in your async toolbox are coroutines. Coroutines are futures that can have values sent to them. + +Like regular futures, code in a coroutine will run until the next `await` point before yielding. This low-level control over asynchronous tasks is quite powerful, allowing for infinitely looping tasks like WebSocket polling, background timers, and other periodic actions. + +## `use_coroutine` + +The `use_coroutine` hook allows you to create a coroutine. Most coroutines we write will be polling loops using await. + +\```rust, no_run +{{#include src/doc_examples/use_coroutine_reference.rs:component}} +\``` + +For many services, a simple async loop will handle the majority of use cases. + +## Yielding Values + +To yield values from a coroutine, simply bring in a `Signal` handle and set the value whenever your coroutine completes its work. + +The future must be `'static` – so any values captured by the task cannot carry any references to `cx`, such as a `Signal`. + +You can use [to_owned](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) to create a clone of the hook handle which can be moved into the async closure. + +\```rust, no_run +{{#include src/doc_examples/use_coroutine_reference.rs:to_owned}} +\``` + +To make this a bit less verbose, Dioxus exports the `to_owned!` macro which will create a binding as shown above, which can be quite helpful when dealing with many values. + +\```rust, no_run +{{#include src/doc_examples/use_coroutine_reference.rs:to_owned_continued}} +\``` + +## Sending Values + +You might've noticed the `use_coroutine` closure takes an argument called `rx`. What is that? Well, a common pattern in complex apps is to handle a bunch of async code at once. With libraries like Redux Toolkit, managing multiple promises at once can be challenging and a common source of bugs. + +With Coroutines, we can centralize our async logic. The `rx` parameter is an Channel that allows code external to the coroutine to send data _into_ the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call "send" on the handle. + +\```rust, no_run +{{#include src/doc_examples/use_coroutine_reference.rs:send}} +\``` + +> Note: In order to use/run the `rx.next().await` statement you will need to extend the [`Stream`] trait (used by [`UnboundedReceiver`]) by adding 'futures_util' as a dependency to your project and adding the `use futures_util::stream::StreamExt;`. + +For sufficiently complex apps, we could build a bunch of different useful "services" that loop on channels to update the app. + +\```rust, no_run +{{#include src/doc_examples/use_coroutine_reference.rs:services}} +\``` + +We can combine coroutines with Global State 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 `Signal::global` or Redux – the only Atoms that need to exist are those that are used to drive the display/UI. + +\```rust, no_run +{{#include src/doc_examples/use_coroutine_reference.rs:global}} +\``` + +Now, in our sync service, we can structure our state however we want. We only need to update the view values when ready. + +\```rust, no_run +{{#include src/doc_examples/use_coroutine_reference.rs:global_continued}} +\``` + +## Automatic injection into the Context API + +Coroutine handles are automatically injected through the context API. You can use the `use_coroutine_handle` hook with the message type as a generic to fetch a handle. + +\```rust, no_run +{{#include src/doc_examples/use_coroutine_reference.rs:injection}} +\``` diff --git a/docs-src/0.5/zh/reference/use_effect.md b/docs-src/0.5/zh/reference/use_effect.md new file mode 100644 index 000000000..e172354ec --- /dev/null +++ b/docs-src/0.5/zh/reference/use_effect.md @@ -0,0 +1,15 @@ +# UseEffect + +[`use_effect`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_effect.html) lets you run a callback after the current render has finished which will be re-run when its dependencies change. Effects are a flexible way to handle side effects like manually changing the DOM. + +If you are looking for a hook to handle async tasks with dependencies, you should use `use_resource` instead. Or if you would like to produce a new value from a callback with dependencies, you should use `use_memo` instead. + +## 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. Effects will automatically subscribe to any signals that you read inside the effect. It will re-run it when any of those signals change. + +## Example + +\```rust, no_run +{{#include src/doc_examples/use_effect.rs}} +\``` diff --git a/docs-src/0.5/zh/reference/use_resource.md b/docs-src/0.5/zh/reference/use_resource.md new file mode 100644 index 000000000..b05323d45 --- /dev/null +++ b/docs-src/0.5/zh/reference/use_resource.md @@ -0,0 +1,37 @@ +# Resource + +[`use_resource`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_resource.html) lets you run an async closure, and provides you with its result. + +For example, we can make an API request (using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html)) inside `use_resource`: + +\```rust +{{#include src/doc_examples/use_resource.rs:use_resource}} +\``` + +The code inside `use_resource` will be submitted to the Dioxus scheduler once the component has rendered. + +We can use `.read()` to get the result of the future. On the first run, since there's no data ready when the component loads, its value will be `None`. However, once the future is finished, the component will be re-rendered and the value will now be `Some(...)`, containing the return value of the closure. + +We can then render that result: + +\```rust +{{#include src/doc_examples/use_resource.rs:render}} +\``` + +\```inject-dioxus +DemoFrame { + use_resource::App {} +} +\``` + +## Restarting the Future + +The `Resource` handle provides a `restart` method. It can be used to execute the future again, producing a new value. + +## Dependencies + +Often, you will need to run the future again every time some value (e.g. a state) changes. Rather than calling `restart` manually, you can read a signal inside of the future. It will automatically re-run the future when any of the states you read inside the future change. Example: + +\```rust, no_run +{{#include src/doc_examples/use_resource.rs:dependency}} +\``` diff --git a/docs-src/0.5/zh/reference/user_input.md b/docs-src/0.5/zh/reference/user_input.md new file mode 100644 index 000000000..eba715fce --- /dev/null +++ b/docs-src/0.5/zh/reference/user_input.md @@ -0,0 +1,68 @@ +# User Input + +Interfaces often need to provide a way to input data: e.g. text, numbers, checkboxes, etc. In Dioxus, there are two ways you can work with user input. + +## Controlled Inputs + +With controlled inputs, you are directly in charge of the state of the input. This gives you a lot of flexibility, and makes it easy to keep things in sync. For example, this is how you would create a controlled text input: + +\```rust, no_run +{{#include src/doc_examples/input_controlled.rs:component}} +\``` + +\```inject-dioxus +DemoFrame { + input_controlled::App {} +} +\``` + +Notice the flexibility – you can: + +- Also display the same contents in another element, and they will be in sync +- Transform the input every time it is modified (e.g. to make sure it is upper case) +- Validate the input every time it changes +- Have custom logic happening when the input changes (e.g. network request for autocompletion) +- Programmatically change the value (e.g. a "randomize" button that fills the input with nonsense) + +## Uncontrolled Inputs + +As an alternative to controlled inputs, you can simply let the platform keep track of the input values. If we don't tell a HTML input what content it should have, it will be editable anyway (this is built into the browser). This approach can be more performant, but less flexible. For example, it's harder to keep the input in sync with another element. + +Since you don't necessarily have the current value of the uncontrolled input in state, you can access it either by listening to `oninput` events (similarly to controlled components), or, if the input is part of a form, you can access the form data in the form events (e.g. `oninput` or `onsubmit`): + +\```rust, no_run +{{#include src/doc_examples/input_uncontrolled.rs:component}} +\``` +\```inject-dioxus +DemoFrame { + input_uncontrolled::App {} +} +\``` +\``` +Submitted! UiEvent { data: FormData { value: "", values: {"age": "very old", "date": "1966", "name": "Fred"} } } +\``` + +## Handling files +You can insert a file picker by using an input element of type `file`. This element supports the `multiple` attribute, to let you pick more files at the same time. You can select a folder by adding the `directory` attribute: Dioxus will map this attribute to browser specific attributes, because there is no standardized way to allow a directory to be selected. + +`type` is a Rust keyword, so when specifying the type of the input field, you have to write it as `r#type:"file"`. + +Extracting the selected files is a bit different from what you may typically use in Javascript. + +The `FormData` event contains a `files` field with data about the uploaded files. This field contains a `FileEngine` struct which lets you fetch the filenames selected by the user. This example saves the filenames of the selected files to a `Vec`: + +\```rust, no_run +{{#include src/doc_examples/input_fileengine.rs:component}} +\``` + +If you're planning to read the file content, you need to do it asynchronously, to keep the rest of the UI interactive. This example event handler loads the content of the selected files in an async closure: + +\```rust, no_run +{{#include src/doc_examples/input_fileengine_async.rs:onchange_event}} +\``` + +Lastly, this example shows you how to select a folder, by setting the `directory` attribute to `true`. + +\```rust, no_run +{{#include src/doc_examples/input_fileengine_folder.rs:rsx}} +\``` \ No newline at end of file diff --git a/docs-src/0.5/zh/reference/web/index.md b/docs-src/0.5/zh/reference/web/index.md new file mode 100644 index 000000000..93a9b5361 --- /dev/null +++ b/docs-src/0.5/zh/reference/web/index.md @@ -0,0 +1,38 @@ +# Web + +To run on the Web, your app must be compiled to WebAssembly and depend on the `dioxus` and `dioxus-web` crates. + +A build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb) but it will load significantly faster because [WebAssembly can be compiled as it is streamed](https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/). + +Examples: + +- [TodoMVC](https://github.com/DioxusLabs/dioxus/blob/main/examples/todomvc.rs) +- [Tailwind App](https://github.com/DioxusLabs/dioxus/tree/main/examples/tailwind) + +[![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/dioxus/blob/main/examples/todomvc.rs) + +> Note: Because of the limitations of Wasm, [not every crate will work](https://rustwasm.github.io/docs/book/reference/which-crates-work-with-wasm.html) with your web apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc). + +## Support + +The Web is the best-supported target platform for Dioxus. + +- Because your app will be compiled to WASM you have access to browser APIs through [wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/introduction.html). +- Dioxus provides hydration to resume apps that are rendered on the server. See the [fullstack](../fullstack/index.md) reference for more information. + +## Running Javascript + +Dioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose. + +For these cases, Dioxus web exposes the use_eval hook that allows you to run raw Javascript in the webview: + +\```rust +{{#include src/doc_examples/eval.rs}} +\``` + +If you are targeting web, but don't plan on targeting any other Dioxus renderer you can also use the generated wrappers in the [web-sys](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html) and [gloo](https://gloo-rs.web.app/) crates. + +## Customizing Index Template + +Dioxus supports providing custom index.html templates. The index.html must include a `div` with the id `main` to be used. Hot Reload is still supported. An example +is provided in the [PWA-Example](https://github.com/DioxusLabs/dioxus/blob/main/examples/PWA-example/index.html). diff --git a/docs-src/0.5/zh/router/example/building-a-nest.md b/docs-src/0.5/zh/router/example/building-a-nest.md new file mode 100644 index 000000000..468f5bd72 --- /dev/null +++ b/docs-src/0.5/zh/router/example/building-a-nest.md @@ -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 +{{#include src/doc_examples/nested_routes.rs:nav}} +\``` + +Next, let's add our `NavBar` component as a layout to our Route enum: + +\```rust +{{#include src/doc_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 `` tag. It takes a target and children. + +Unlike a regular `` 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 +{{#include src/doc_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 +{{#include src/doc_examples/dynamic_route.rs:blog}} +\``` + +Now we'll create another index component, that'll be displayed when no blog post +is selected: + +\```rust +{{#include src/doc_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 +{{#include src/doc_examples/dynamic_route.rs:blog_post}} +\``` + +Finally, let's tell our router about those components: + +\```rust +{{#include src/doc_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/components/fn.Link.html diff --git a/docs-src/0.5/zh/router/example/first-route.md b/docs-src/0.5/zh/router/example/first-route.md new file mode 100644 index 000000000..999cea9bd --- /dev/null +++ b/docs-src/0.5/zh/router/example/first-route.md @@ -0,0 +1,69 @@ +# 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. + + +Routable is a trait for anything that can: +- Be parsed from a URL +- Be turned into a URL +- Be rendered as to a Element + + +Let's create a new router. First, we need an actual page to route to! Let's add a homepage component: + +\```rust +{{#include src/doc_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 +{{#include src/doc_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 +{{#include src/doc_examples/catch_all.rs:fallback}} +\``` + +Next, register the route in the Route enum to match if all other routes fail. + +\```rust +{{#include src/doc_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. diff --git a/docs-src/0.5/zh/router/example/full-code.md b/docs-src/0.5/zh/router/example/full-code.md new file mode 100644 index 000000000..a1f9aba02 --- /dev/null +++ b/docs-src/0.5/zh/router/example/full-code.md @@ -0,0 +1,5 @@ +# Full Code + +\```rust +{{#include src/doc_examples/full_example.rs}} +\``` diff --git a/docs-src/0.5/zh/router/example/index.md b/docs-src/0.5/zh/router/example/index.md new file mode 100644 index 000000000..f30a04668 --- /dev/null +++ b/docs-src/0.5/zh/router/example/index.md @@ -0,0 +1,28 @@ +# 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/learn/0.5/getting_started) 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. diff --git a/docs-src/0.5/zh/router/example/navigation-targets.md b/docs-src/0.5/zh/router/example/navigation-targets.md new file mode 100644 index 000000000..a06fa7f8f --- /dev/null +++ b/docs-src/0.5/zh/router/example/navigation-targets.md @@ -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 +{{#include src/doc_examples/external_link.rs:component}} +\``` + +[`External`]: https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.External +[`Internal`]: https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.Internal +[`NavigationTarget`]: https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html diff --git a/docs-src/0.5/zh/router/example/redirection-perfection.md b/docs-src/0.5/zh/router/example/redirection-perfection.md new file mode 100644 index 000000000..f208d94b2 --- /dev/null +++ b/docs-src/0.5/zh/router/example/redirection-perfection.md @@ -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 +{{#include src/doc_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/main/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. diff --git a/docs-src/0.5/zh/router/index.md b/docs-src/0.5/zh/router/index.md new file mode 100644 index 000000000..af9cc26a6 --- /dev/null +++ b/docs-src/0.5/zh/router/index.md @@ -0,0 +1,38 @@ +# Introduction + +> If you are not familiar with Dioxus itself, check out the [Dioxus guide](../guide/index.md) 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@0.5.0 --features router +\``` + +Then, add this to your `Dioxus.toml` (learn more about configuration [here](../CLI/configure)): + +\```toml +[web.watcher] +index_on_404 = true +\``` + +> This configuration only works when using `dx serve`. If you host your app in a different way (which you most likely do in production), you need to find out how to add a fallback 404 page to your app, and make it a copy of the generated `dist/index.html`. + +This will instruct `dx serve` to redirect any unknown route to the index, to then be resolved by the router. +The router works on the client. If we connect through the index route (e.g., `localhost:8080`, then click a link to go to `localhost:8080/contact`), the app renders the new route without reloading. +However, when we go to a route *before* going to the index (go straight to `localhost:8080/contact`), we are trying to access a static route from the server, but the only static route on our server is the index (because the Dioxus frontend is a Single Page Application) and it will fail unless we redirect all missing routes to the index. + +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/). diff --git a/docs-src/0.5/zh/router/lib.rs b/docs-src/0.5/zh/router/lib.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/docs-src/0.5/zh/router/lib.rs @@ -0,0 +1 @@ + diff --git a/docs-src/0.5/zh/router/reference/history-buttons.md b/docs-src/0.5/zh/router/reference/history-buttons.md new file mode 100644 index 000000000..110ded948 --- /dev/null +++ b/docs-src/0.5/zh/router/reference/history-buttons.md @@ -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 +{{#include src/doc_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. diff --git a/docs-src/0.5/zh/router/reference/history-providers.md b/docs-src/0.5/zh/router/reference/history-providers.md new file mode 100644 index 000000000..3a16bcba1 --- /dev/null +++ b/docs-src/0.5/zh/router/reference/history-providers.md @@ -0,0 +1,21 @@ +# 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 [`LiveviewHistory`] is a custom implementation that works with the liveview renderer. +- 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 +{{#include src/doc_examples/history_provider.rs:app}} +\``` + diff --git a/docs-src/0.5/zh/router/reference/index.md b/docs-src/0.5/zh/router/reference/index.md new file mode 100644 index 000000000..85a1357a1 --- /dev/null +++ b/docs-src/0.5/zh/router/reference/index.md @@ -0,0 +1,23 @@ +# Adding the router to your application + +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 +{{#include src/doc_examples/first_route.rs:router}} +\``` + +Then we render the router with the [`Router`] component. + +\```rust +{{#include src/doc_examples/first_route.rs:app}} +\``` diff --git a/docs-src/0.5/zh/router/reference/layouts.md b/docs-src/0.5/zh/router/reference/layouts.md new file mode 100644 index 000000000..6e45c59ad --- /dev/null +++ b/docs-src/0.5/zh/router/reference/layouts.md @@ -0,0 +1,43 @@ +# 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`]. + +This page is built with the Dioxus. It uses Layouts in several different places. Here is an outline of how layouts are used on the current page. Hover over different layouts to see what elements they are on the page. + +\```inject-dioxus +LayoutsExplanation {} +\``` + +Here is a more complete example of a layout wrapping the body of a page. + +\```rust +{{#include src/doc_examples/outlet.rs:outlet}} +\``` + +The example above will output the following HTML (line breaks added for +readability): + +\```html +
header
+

Index

+
footer
+\``` + +## Layouts with dynamic segments + +You can combine layouts with [nested routes](./routes/nested.md) to create dynamic layouts with content that changes based on the current route. + +Just like routes, layouts components must accept a prop for each dynamic segment in the route. For example, if you have a route with a dynamic segment like `/:name`, your layout component must accept a `name` prop: + +\```rust +{{#include src/doc_examples/outlet.rs:outlet_with_params}} +\``` + +Or to get the full route, you can use the [`use_route`](https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_route.html) hook. + +\```rust +{{#include src/doc_examples/outlet.rs:outlet_route}} +\``` diff --git a/docs-src/0.5/zh/router/reference/navigation/index.md b/docs-src/0.5/zh/router/reference/navigation/index.md new file mode 100644 index 000000000..b3eb8754f --- /dev/null +++ b/docs-src/0.5/zh/router/reference/navigation/index.md @@ -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 +
Link to an other page +\``` + +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 +{{#include src/doc_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. diff --git a/docs-src/0.5/zh/router/reference/navigation/programmatic.md b/docs-src/0.5/zh/router/reference/navigation/programmatic.md new file mode 100644 index 000000000..c3d69e9b0 --- /dev/null +++ b/docs-src/0.5/zh/router/reference/navigation/programmatic.md @@ -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 [`navigator`] function which 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 +{{#include src/doc_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. diff --git a/docs-src/0.5/zh/router/reference/redirects.md b/docs-src/0.5/zh/router/reference/redirects.md new file mode 100644 index 000000000..65505ee46 --- /dev/null +++ b/docs-src/0.5/zh/router/reference/redirects.md @@ -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 +{{#include src/doc_examples/full_example.rs:router}} +\``` diff --git a/docs-src/0.5/zh/router/reference/routes/index.md b/docs-src/0.5/zh/router/reference/routes/index.md new file mode 100644 index 000000000..4de6bfee0 --- /dev/null +++ b/docs-src/0.5/zh/router/reference/routes/index.md @@ -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 +{{#include src/doc_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 +{{#include src/doc_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 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 +{{#include src/doc_examples/catch_all_segments.rs:route}} +\``` + +## Query Segments + +Query segments are in the form of `?:name&:othername` where `name` and `othername` are the names of fields 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 `FromQueryArgument`. + +Query segments must be the _after all route segments_ and cannot be included in nests. + +\```rust +{{#include src/doc_examples/query_segments.rs:route}} +\``` diff --git a/docs-src/0.5/zh/router/reference/routes/nested.md b/docs-src/0.5/zh/router/reference/routes/nested.md new file mode 100644 index 000000000..c61af656b --- /dev/null +++ b/docs-src/0.5/zh/router/reference/routes/nested.md @@ -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](./#catch-all-segments) +2. Contain a [Query Segment](./#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 +{{#include src/doc_examples/nest.rs:route}} +\``` diff --git a/docs-src/0.5/zh/router/reference/routing-update-callback.md b/docs-src/0.5/zh/router/reference/routing-update-callback.md new file mode 100644 index 000000000..5bac3829c --- /dev/null +++ b/docs-src/0.5/zh/router/reference/routing-update-callback.md @@ -0,0 +1,17 @@ +# 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, 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 +{{#include src/doc_examples/routing_update.rs:router}} +\``` diff --git a/docs-src/0.5/zh/router/reference/static-generation.md b/docs-src/0.5/zh/router/reference/static-generation.md new file mode 100644 index 000000000..756732b27 --- /dev/null +++ b/docs-src/0.5/zh/router/reference/static-generation.md @@ -0,0 +1,19 @@ +# 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 +{{#include src/doc_examples/static_generation.rs}} +\``` + +## Example + +- [examples/static-hydrated](https://github.com/DioxusLabs/dioxus/tree/main/packages%2Ffullstack%2Fexamples%2Fstatic-hydrated)