拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Dioxus 副作用钩子完全指南:use_effect、use_reactive 与 mounted 节点操作实战

Dioxus 副作用钩子完全指南use_effect、use_reactive 与 mounted 节点操作实战【免费下载链接】dioxusFullstack app framework for web, desktop, and mobile.项目地址: https://gitcode.com/GitHub_Trending/di/dioxus副作用side effect泛指组件渲染之外的一切外部操作——手动更新 DOM、向浏览器 API 发号施令、写日志、同步外部存储。本文围绕 Dioxus 官方hooks包中 side_effects.md 的核心设计系统讲解use_effect钩子的响应式订阅机制、非响应式依赖注入use_reactive以及结合onmounted事件操作真实 DOM 的完整方案。读完本文你将掌握在 Dioxus 组件中正确触达渲染之外世界的方法并清楚知道何时该用 effect、何时应改用use_memo或use_resource从而规避最常见的状态更新陷阱。什么是 Effect渲染完成之后运行的响应式闭包Effect 是在组件完成渲染之后运行的响应式闭包。Dioxus 把读取响应式值与对外部世界产生作用严格区分开组件渲染负责把信号转换成 UI 描述而 effect 负责把这些变化带到 DOM、浏览器或其他外部系统中去。官方文档side_effects.md给出了它的典型应用场景使用web-sys或 JavaScript 在渲染完成后手动更新 DOM从渲染完成的 DOM 中读取某个值例如测量元素尺寸。一个非常关键的设计约束被放在文档开头醒目的位置Effects are specifically created for side effects. If you are trying to derive state, use a memo or resource instead.也就是说如果你是想根据已有状态派生出新状态请改用use_memo同步派生或use_resource异步派生而不是在 effect 里再去写另一个信号——那样会造成多余的渲染与难以追踪的更新链路。相关对比可参考 derived_state.md 与 use_resource.md。仓库中 examples/04-managing-state/use_effect.rs 对使用边界做了同样的强调Its the escape hatch for talking to code outside of Dioxus — logging, syncing to localStorage, updating the document.title, tweaking imperative APIs, and so on. For pure derivations of other signals, reach for use_memo instead.它是与 Dioxus 之外的代码对话的逃生舱——日志、同步 localStorage、更新 document.title、调整命令式 API 等纯信号的派生请改用 use_memo。use_effect 的基础用法向 canvas 写入文本use_effect是hooks包暴露的官方钩子通过dioxus::prelude即可导入。它接收一个闭包在组件首次挂载完成后执行一次之后每当闭包内读取过的信号发生变化就重新执行。下面这段来自官方文档的示例演示了完整闭环信号count驱动按钮点击递增effect 则在每次渲染后把最新值通过 JavaScript 画到canvas上use dioxus::prelude::*; fn MyComponent() - Element { let mut count use_signal(|| 0); use_effect(move || { // Effect 与 memo、resource 一样是响应式的。 // 如果在 effect 内部读取了某个值当该值变化时 effect 会重新运行 let count count.read(); // 使用 count 值手动更新 DOM document::eval(format!( r#var c document.getElementById(dioxus-canvas); var ctx c.getContext(2d); ctx.font 30px Arial; ctx.fillText({count}, 10, 50);# )); }); rsx! { button { // 点击按钮后 count 递增effect 会随之重新运行 onclick: move |_| count 1, Increment } canvas { id: dioxus-canvas, } } }要点拆解闭包通过move捕获count而真正让 effect 与信号建立联系的是闭包体内对count.read()或count()的实际调用——只有被读到的信号才会成为依赖。effect 的运行时机在组件渲染完成后因此示例中id: dioxus-canvas的canvas此时已经存在于真实 DOM 中document.getElementById才能拿到它。若因组件提前 return 而跳过了use_effect调用该 effect 将不再被激活——这与下方规则 of hooks中关于调用顺序的要求一脉相承。源码视角use_effect 是如何工作的理解底层实现有助于把握 effect 的语义边界。packages/hooks/src/use_effect.rs 的完整实现只有约 50 行核心逻辑如下let callback use_callback(move |_| callback()); let location std::panic::Location::caller(); use_hook(|| { // 在 effect 内部跟踪所有读取以便某个被读取的值变化时重新运行 effect let (rc, mut changed) ReactiveContext::new_with_location(location); // 去重排队中的 effect let effect_queued Rc::new(Cell::new(false)); // 生成一个任务在以下时机运行 effect // 1) 组件首次运行 // 2) 因异步读取而在任意时刻被触发重跑 // 3) 与组件重跑在同一 tick 内被触发需要等组件先重跑完再执行 effect let queue_effect_for_next_render move || { if effect_queued.get() { return; } effect_queued.set(true); let effect_queued effect_queued.clone(); queue_effect(move || { rc.reset_and_run_in(|| callback(())); effect_queued.set(false); }); }; queue_effect_for_next_render(); spawn(async move { loop { // 等待响应式上下文变化 let _ changed.next().await; queue_effect_for_next_render(); } }); Effect { rc } })从这段实现可以读出四条关键设计首次必然执行queue_effect_for_next_render()在注册钩子时即被调用所以 effect 在首次挂载后一定会运行读取即订阅effect 通过ReactiveContext记录闭包执行期间读到的所有信号任一依赖变化都会让changed流产生一个事件从而触发下一次排队执行渲染后执行同一 tick 内的多次信号变更会被去重合并effect_queued标志位且重跑被调度到组件渲染之后——这正是文档强调run after the component has finished rendering的底层保证也是为什么 effect 内读取到的一定是最新一次渲染对应的状态返回句柄use_effect返回一个EffectCopy类型其mark_dirty()方法可手动将 effect 标记为脏使其在下一次渲染时重跑——适合无法在闭包内直接读取、但逻辑上依赖某次更新的场景。用 use_reactive 注入非响应式依赖use_effect的响应式依赖来自闭包内的信号读取。那么问题来了如果依赖的是一个普通 Rust 值比如组件 props 里的u32、外部传入的结构体而非Signaleffect 该如何感知它的变化答案是use_reactive。文档给出的建议很直接要添加非响应式依赖可以使用use_reactive()钩子。信号会被自动加入依赖因此不需要对它们调用此方法。use dioxus::prelude::*; # async fn sleep(delay: u32) {} #[component] fn Comp(count: u32) - Element { // 因为 memo 把 count 作为依赖订阅count 每次变化 memo 都会重跑 use_effect(use_reactive((count,), |(count,)| println!(Manually manipulate the dom))); todo!() }use_reactive的工作方式非常巧妙它把一组非响应式数据伪装成可订阅的依赖。具体过程见 packages/hooks/src/use_reactive.rslet mut last_state use_signal(|| { first_run true; non_reactive_data.out() }); if !first_run non_reactive_data.changed(*last_state.peek()) { last_state.set(non_reactive_data.out()) } move || closure(last_state())其机制为内部用一个信号暂存non_reactive_data的上一次输出每次调用时通过Dependency::changed用PartialEq比较新旧值一旦发现变化就把新值写入内部信号而返回的闭包每次执行时都会读取该信号。由于信号被读取包裹它的use_effect就能照常响应——非响应式数据就这样被接入了响应式依赖图。依赖实现的范围与细节Dependencytrait 由库自动为不超过 8 个元素、元素实现PartialEq Clone的元组实现impl_dep!宏从 1 元组一直展开到 8 元组。带引用的元素A也有特化实现克隆后的值即为输出Dependency for A输出类型是A克隆值Dependency for (A, B, ...)最多 8 个引用元素的元组逐个比较是否变化。如果误用了未实现Dependency的类型编译器会给出清晰的诊断提示代码中通过diagnostic::on_unimplemented注明Dependencyis automatically implemented for all tuples with less than 8 references...。use_reactive还提供了一个声明式宏use_reactive!避免手写把参数放进元组再在闭包里解包的样板use_effect(use_reactive!(|data| { println!(Data changed: {}, data); }));宏会把参数列表展开成(data, ...)的元组与对应的解构闭包见 packages/hooks/src/use_reactive.rs 中#[macro_export]部分。修改已挂载节点use_effect onmounted 组合拳Effect 最常见的落地场景之一是读取或修改渲染出来的真实 DOM。Dioxus 为每个元素提供了onmounted事件当元素首次被加入 DOM时触发并把代表该真实元素的MountedEvent传给回调事件定义位于 packages/html/src/events/mounted.rs其中pub type MountedEvent EventMountedData;配套事件声明见 packages/html/src/events/generated.rs。官方文档给出的组合方案是用onmounted把元素句柄存进Signal再让use_effect订阅该信号与文本信号从而在两者都就绪时执行 DOM 测量use dioxus::prelude::*; fn MyComponent() - Element { let mut current_text use_signal(String::new); let mut mounted_text_div: SignalOptionMountedEvent use_signal(|| None); let mut rendered_size use_signal(String::new); use_effect(move || { // 如果文本 div 已挂载我们就可以读取它的宽度 if let Some(div) mounted_text_div() { // 在 effect 内部而非 spawn 内部读取文本 // 以便 effect 订阅到该信号 let text current_text(); spawn(async move { let bounding_box div.get_client_rect().await; rendered_size.set(format!({text} is {bounding_box:?})); }); } }); rsx! { input { // 输入文字会更新 current_text 信号effect 因订阅它而重跑 oninput: move |evt| current_text.set(evt.value()), placeholder: Enter text here, value: {current_text} } // 文字变化会改变这个 div 的尺寸 div { onmounted: move |element| { mounted_text_div.set(Some(element.clone())); }, {current_text} } {rendered_size} } }这段示例蕴含了几个值得反复品味的工程细节两阶段协作onmounted只负责捕获元素句柄在挂载瞬间触发一次测量逻辑全部由use_effect驱动。首次挂载后 effect 运行此时信号可能还是None事件回调先于或后于 effect 取决于调度effect 会先静默跳过当onmounted写入mounted_text_div后effect 因读到新值而重跑测量随即开始。订阅位置决定重跑时机注释点明了在 effect 内、spawn 外读取current_text的原因——只有直接在 effect 闭包体内读取信号该信号才会被ReactiveContext记录为依赖后续输入变化才能触发重跑若把读取挪进spawn的异步闭包effect 就不会订阅它了。异步结果写回信号get_client_rect().await返回的是异步的尺寸查询结果测量完成后通过rendered_size.set(...)写回 UI。注释第 75 行的设计同时保证了 effect 语义正确这是本模式中最容易写错、也最值得借鉴的一点。value: {current_text}输入框与 div 显示绑定同一信号用户输入即重渲染为 effect 创造了真实的变化触发源。使用边界与注意事项不要用 effect 派生状态文档开篇的告诫在实践中最容易被违反在 effect 里set另一个信号来计算值不仅会引入多余的渲染轮次还可能造成 effect → 信号 → 重渲染 → effect 的连锁循环。请按类型选择工具需求正确工具依据文档同步派生新状态如count * 2use_memoderived_state.md异步派生状态如发起请求并返回结果use_resourceuse_resource.md对外部世界产生副作用DOM、日志、存储use_effect本文订阅 props 等非响应式数据并做派生use_memouse_reactive同上遵循 Rules of Hooksuse_effect是标准钩子因此必须遵守 rules_of_hooks.md 中的规则只在组件根层级或其他钩子内调用不要放进条件、循环、事件处理器或其他钩子的初始化闭包里每次渲染保持相同的调用顺序——Dioxus 按调用顺序把状态存入组件内的列表顺序错乱会导致状态张冠李戴甚至 panic命名以use_开头便于在代码审查时识别调用点。docs 文档通过#[doc include_str!(../docs/rules_of_hooks.md)]被拼接到use_effect的 API 文档中见 packages/hooks/src/use_effect.rs 第 8-9 行因此即使在docs.rs上阅读函数签名也能看到完整的规则说明。Effect 与组件销毁、错误处理如果需要在组件销毁时执行清理注销监听器、断开连接等可结合use_on_destroy等钩子相关实现见 packages/hooks/src 目录下的对应模块文档明确若use_effect调用因提前 return 被跳过effect 将不再激活因此在条件渲染分支较多时尤其要注意钩子位置稳定。从文档到一行命令的验证想立刻上手验证本文所有机制最直接的方式是运行仓库内置示例cargo run --example use_effect该示例位于 examples/04-managing-state/use_effect.rs展示了两个独立 effect 分别订阅count与name count并在每次变化时向终端打印日志与本文的 canvas 示例互为印证。把示例中的println!换成document::eval或onmounted测量逻辑即可在真实工程中复现本文讨论的全部模式。小结use_effect是 Dioxus 中渲染世界与外部世界之间的桥梁。掌握它需要抓住三条主线时机渲染完成后执行同 tick 去重排队、依赖体内读到的信号即依赖非响应式数据用use_reactive包装、边界不做状态派生DOM 交互与onmounted组合使用。结合 use_effect.rs 的底层实现与 side_effects.md 的语义约定你可以精确预测 effect 何时运行、为何运行写出既可读又无多余渲染的 Dioxus 应用。【免费下载链接】dioxusFullstack app framework for web, desktop, and mobile.项目地址: https://gitcode.com/GitHub_Trending/di/dioxus创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门