Dioxus 0.7 全栈实证陷阱(rsx / hooks / server fn)

沉淀自 2026-07~10 的 Yggdrasil 会话记忆(Dioxus 0.7.9/0.7.10 实测)。记忆只是线索,落地前以当前源码与依赖版本复核。

1. rsx! 组件调用禁止外层花括号

{ Component { props } } 会被解析成宏内部 _completions::Component::Name 结构体的字面量构造,报错组合:

  • E0559 no field named X
  • all struct fields are already assigned
  • E0277 IntoDynNode not satisfied

错误数按「调用点 × 字段数」级联(6 个调用 × 6 字段 = 36 个错误),极易误判为组件定义错误。 判别:错误里出现 _completions::Component 即花括号包裹问题。修法:直接写 Component { props }。

2. 原生元素 oninput 必须用 EventFormData

dioxus-html 0.7.10 的 impl_event:原生元素的内联闭包走 explicit closure,要求 FnMut(EventFormData);Fn(String) 的值提取只对组件 prop(如 EventHandlerString)生效。

input { oninput: move |e: EventFormData| { let v = e.value(); } } // 原生元素
FormInput { oninput: move |v: String| { /* ... */ } }             // 组件 prop

FormInput 内部正是 oninput: move |e| oninput.call(e.value())。

3. #[server] 在 wasm32 构建会剥离函数体

所以 server fn 体内可以无 cfg 门控地调用 #[cfg(feature = "server")] 函数,wasm 编译不报未解析。 推论:只在函数体里用到的 import/类型,在 wasm-only check(--target wasm32-unknown-unknown --no-default-features --features web)会报 dead_code/unused,需按 cfg 拆分 import,纯 server 辅助函数整体 gate。该 check 自带约 10 个同类基线警告,属噪音;CI 卡点是 cargo clippy --all-features -- -D warnings。

4. use_future 没有响应式依赖,不会自动重启

核实于 dioxus-hooks 0.7.9 use_future.rs:45-79:闭包经 callback(()) 只调一次,无 ReactiveContext;restart()(:110)必须手动调用。只有 use_resource(use_resource.rs:38-104)内置 ReactiveContext,依赖变化时 cancel + 重启。

踩坑形态:把 signal 快照进 use_future 的异步体、只查一次就 return,指望「signal 变化时重跑」实现轮询 → 永不启动。实例:待审核评论徽章在 DB 已 approved 后仍残留 20h;备份按钮 busy 永真。 正确模式:长生命周期 loop + 循环内每次 live 读 signal,无事可做才 return。

5. use_hook 每次渲染 clone 存储值

use_hook(|| Cell::new(0)) 拿到的 Cell 是按值拷贝,各闭包持互不同步的副本,set 写不回 hook 存储。共享可变态必须 RcCellT / RcRefCellT(Popover 即此模式)。

6. 原生 DOM 事件监听里 spawn 会 panic

wasm_bindgen Closure 挂 window.addEventListener 后调 spawn → with_current_scope → current_scope_id().unwrap()(runtime.rs:222)对空 scope 栈 panic(panic=abort 下浏览器报 unreachable)。Dioxus 自己的 onclick 等有 scope 上下文,不受影响。 修法(仓库内先例 src/pages/admin/system/sql_console.rs:97-121):渲染期取

let scope_id = dioxus::core::Runtime::current()
    .try_current_scope_id()
    .unwrap_or(dioxus::core::ScopeId::ROOT);

handler 内 Runtime::current().in_scope(scope_id, move || ...);Rc 句柄逐事件克隆进内层闭包。注意 dioxus::prelude 不含 Runtime/current_scope_id,要走 dioxus::core:: 全路径。

7. 条件字符串 prop 会让整个 rsx! 报 E0308

TagChip(variant: if flag { "text" } else { "outline" }) 报 expected &str, found String,定位落在整个宏而非 prop。 修法:在 render 纯计算区先 bind let variant = if flag { "text" } else { "outline" }; 再传 variant: variant。不要为迁就宏把静态 prop 改成 String,也不要加 signal。

附:&str 字面量 → String prop 自动 coerce

组件 prop 声明 label: String 后,30+ 处 &str 字面量调用点经 Into 自动转换、不受影响;把 &'static str 放宽成 String 是可行手段(FormLabel 先例)。