状态与数据流(页面加载策略、全局状态、刷新边界)
1) 快速定位
- 全局状态定义:
src/state/app_state.rs - 状态注入:
src/app.rs(provide_app_state()) - 页面数据加载:
src/pages/* - 数据访问:
src/db/*_repo.rs
2) AppState 的职责与边界
AppState(src/state/app_state.rs)包含:
assets: Vec<Asset>snapshots: Vec<Snapshot>plans: Vec<AllocationPlan>active_plan: Option<AllocationPlan>loading: boolerror: Option<String>is_inventory_mode: bool(当前更多是 UI 语义,部分页面也有局部 state)
推荐理解方式:
- 数据库是事实来源(source of truth)
- AppState 是 UI 缓存(cache)
- 读 DB 后写入 state,页面渲染从 state 读取
- 写 DB 成功后刷新 state(避免脏 UI)
3) 目前的加载策略(以代码为准)
不同页面采用了不同的“初始加载”风格:
HistoryPage/AnalysisPage:使用use_effect在初始渲染时加载 DB 数据HomePage:使用loadedsignal 做“一次性执行”保护,并在渲染路径中读取 DB(学习期能跑通,但工程上更推荐 use_effect)AssetsPage:更多依赖AppState.assets,并在保存/删除后触发 refreshPlansPage:激活/保存/删除后重新拉取plans并写入 state
这说明项目仍处于“可快速迭代”的阶段,统一数据加载层(Service/Store)仍有空间。
4) 推荐的数据流模式(适合逐步演进)
4.1 最小可控模式(现阶段推荐)
- 页面初始化:从 Repository 拉数据 → 写入 AppState
- 发生写操作(保存/删除/激活):写 DB 成功 → 重新查询 → 写入 AppState
优点:
- 简单可靠,不需要复杂缓存一致性策略
缺点:
- 重复查询较多(但对个人资产规模通常足够)
4.2 进阶模式(后续可做)
- 引入
services/*作为业务入口:- 页面只调用 service
- service 负责 DB + 校验 + 错误类型 + 刷新 state
这会显著减少页面层的重复逻辑。
5) 与所有权/借用相关的注意点(Dioxus Signal)
Signal<AppState> 的 .read()/.write() 会持有内部借用句柄。
建议:
- 缩小借用作用域:读完就 drop,再写
- 必要时 clone:UI 渲染需要拥有数据(尤其跨作用域/闭包)时,用 clone 换取简单性
- 避免在渲染路径写 state:优先 use_effect
6) 典型业务流(从入口到落库)
6.1 新增资产
- UI:
AssetsPage→AssetForm(src/pages/assets.rs/src/components/asset/asset_form.rs) - 写库:
AssetRepository::insert(&asset) - 刷新:
AssetRepository::find_all()→state.write().assets = ...
6.2 发起盘点(生成快照)
- UI:
InventoryMode(src/components/snapshot/inventory_mode.rs) - 写库:
SnapshotRepository::create(&snapshot)+ 更新资产市值(若实现) - 刷新:重新读取 assets/snapshots 写入 state
6.3 切换激活方案
- UI:
PlansPage触发 - 写库:
PlanRepository::set_active(plan_id) - 刷新:
PlanRepository::find_all()→ 更新state.plans(并推导active_plan)
7) 建议的可观测性(学习期非常有帮助)
- 把写库失败写入
state.error并在 Layout 顶部展示(而不是只打印) - 对重要操作(创建快照、激活方案)记录一条可读日志(先
println!,后续可引入日志库)