08 Dioxus 入门:组件、props、Signal、路由与数据流
本章目标
- 你能读懂 Dioxus 的组件写法(
#[component]+rsx!)。 - 你能理解本项目的路由结构与布局组件。
- 你能掌握
Signal的基本用法:全局状态(Context)与页面局部状态(use_signal)。 - 你能在
asset-light里新增一个简单页面/组件并接入导航。
1. Dioxus 的核心概念(用 React 类比)
如果你熟悉 React,可以这样对照:
- 组件函数:React function component ⇔
#[component] fn Foo() -> Element - JSX:JSX ⇔
rsx! { ... } - Props:props ⇔
#[derive(Props)]的 struct 或组件参数 - State:useState ⇔
use_signal - Effect:useEffect ⇔
use_effect - Router:react-router ⇔
dioxus_router的Routable+Router
2. 项目路由与布局
2.1 根组件
文件:src/app.rs
关键点:
provide_app_state()注入全局状态(Context)Router::<Route> {}渲染路由
2.2 路由枚举
文件:src/router.rs
你会看到:
#[derive(Routable)] pub enum Route { ... }#[layout(Layout)]:用布局组件包裹多个页面
这相当于:所有页面都嵌在同一个 Layout(含 Sidebar)中。
2.3 Sidebar 导航
文件:src/components/layout/sidebar.rs
关键点:
Link { to: Route::HomePage {}, ... }实现导航- 用
use_route::<Route>()获取当前路由 - 用
std::mem::discriminant做“当前页面高亮”(按 enum variant 判断)
3. 状态管理:全局 Signal 与局部 Signal
3.1 全局状态 AppState
文件:src/state/app_state.rs
provide_app_state():提供Signal<AppState>use_app_state():在任意组件中获取它
你会在页面里看到典型写法:
let mut state = use_app_state();let assets = state.read().assets.clone();state.write().assets = new_assets;
关键学习点(和所有权/借用相关):
.read()/.write()持有内部借用句柄,尽量缩短作用域- UI 渲染往往会
clone()数据用于展示(学习期可以接受)
3.2 页面局部状态:use_signal
典型场景:模态框开关、编辑态、选择态。
例如 src/pages/assets.rs 里:
show_form: Signal<bool>控制表单 modalediting_asset: Signal<Option<Asset>>当前编辑的资产inventory_mode: Signal<bool>盘点模式开关
你可以把它类比为 React 的:
const [showForm, setShowForm] = useState(false)
4. 副作用:加载数据与刷新
4.1 use_effect:在渲染之外做 IO
例子:src/pages/history.rs / src/pages/analysis.rs 使用 use_effect 在初始渲染时加载 DB 数据。
这类逻辑建议尽量放在 use_effect 里,避免“在 render 路径里写 state”导致的重复执行或难排查问题。
4.2 本项目里的一种“loaded flag”模式
你会在 src/pages/home.rs 看到:
- 用
loaded: Signal<bool>保护“只执行一次”的加载逻辑
这在学习期能跑通,但从工程最佳实践看,你可以把它作为练习:
把 HomePage 的初始加载重构为
use_effect,并理解它与 loaded-flag 的差别。
5. 事件处理与回调(EventHandler)
在 Dioxus 里,事件回调通常是 closure:
on_click: move |_| { ... }
而组件间传递回调,常用 EventHandler<T>(你会在多处组件 props 中见到)。
这和 React 传 onSave={(x) => ...} 很像,但 Rust 的 closure 需要更明确的所有权移动(move)。
6. 本章练习(强烈推荐做)
练习 A:新增一个“设置页”占位并接入导航
目标:走一遍“新增页面 → 路由 → Sidebar”的完整流程。
建议步骤:
- 新建
src/pages/settings.rs,写一个最小页面组件(只显示标题即可) - 在
src/pages/mod.rs里导出它 - 在
src/router.rs的Routeenum 增加一个SettingsPage {}路由 - 在
src/components/layout/sidebar.rs增加一个 NavItem 链接
练习 B:在页面顶部渲染一个全局错误提示条
目标:把上一章的“错误处理”落到 UI 体验中。
思路:
AppState有error: Option<String>- 在 Layout 或每个页面顶部,如果有 error 就渲染一个红色 banner,并提供关闭按钮(清空 error)
练习 C(进阶):把 HomePage 的加载逻辑改成 use_effect
目标:理解“副作用与渲染”的边界,避免重复加载与不可控状态写入。