
Dioxus use_resource 完全指南响应式异步数据加载的订阅、取消与生命周期管理【免费下载链接】dioxusFullstack app framework for web, desktop, and mobile.项目地址: https://gitcode.com/GitHub_Trending/di/dioxususe_resource()是 Dioxus 中基于 future 的响应式 hook它会把一个异步任务包装成组件内可读的响应式值一旦你在该 future 内部读取了任何 signal之后对这些 signal 的写入都会自动触发 future 重新运行从而让异步数据随依赖状态自动刷新成为框架原生能力。阅读本文后你将掌握 resource 的创建、.read()/.state()读取、.restart()/.cancel()/.pause()/.resume()生命周期控制以及use_reactive!处理非响应式依赖的方法并理解它与use_future、use_memo的本质区别。基本用法把 future 变成响应式数据use_resource接受一个返回 future 的闭包并返回一个ResourceT句柄。以天气查询为例只要 future 内部通过country()读取了country这个 signalresource 就会自动订阅它的变化use dioxus::prelude::*; async fn get_weather(location: WeatherLocation) - ResultString, String { Ok(Sunny.to_string()) } fn app() - Element { let country use_signal(|| WeatherLocation { city: Berlin.to_string(), country: Germany.to_string(), coordinates: (52.5244, 13.4105), }); // 因为 future 内部通过 country.read() 读取了 country // 每次 country 变化resource 的 future 都会重新运行并产出新值。 let current_weather use_resource(move || async move { get_weather(country()).await }); rsx! { // resource 的值可被轮询 // Some(Ok(_)) 表示已成功、Some(Err(_)) 表示出错、None 表示仍在运行 match *current_weather.read_unchecked() { Some(Ok(weather)) rsx! { WeatherElement { weather } }, Some(Err(e)) rsx! { p { Loading weather failed, {e} } }, None rsx! { p { Loading... } } } } } #[derive(Clone)] struct WeatherLocation { city: String, country: String, coordinates: (f64, f64), } #[component] fn WeatherElement(weather: String) - Element { rsx! { p { The weather is {weather} } } }这段代码揭示了 resource 的三种读取状态与 UI 渲染的对应关系——在示例 packages/hooks/src/use_resource.rs 中其内部核心实现也印证了这一点use_resource用use_signal分别维护了一个value: SignalOptionT与state: SignalUseResourceState任务尚未完成时值为None完成后才写入Some(res)。因此 UI 层天然可以用一个match覆盖加载中 / 成功 / 失败三种分支。响应式运行机制读即订阅写即重跑use_resource的响应式能力来自 Dioxus 的ReactiveContext。在实现中每次 future 启动前都会调用rc.reset_and_run_in(mut future)并把 future 的每一次 poll 都放进rc.run_in(...)的作用域里执行见 packages/hooks/src/use_resource.rs。这意味着只要 future 执行期间读取了任何 signal该读取就会被自动记录为依赖同时一个监听循环不断等待依赖变化信号一旦触发就取消旧任务task.write().cancel()并启动新任务task.set(cb(()))。逐步观察 resource 的生命周期下面的流程展示了依赖写入如何驱动 resource 重跑以及如何用.state()与直接.read()观察其状态# use dioxus::prelude::*; // 创建一个计数 signal let mut count use_signal(|| 1); // 创建一个把 count 翻倍的 resource let double_count use_resource(move || async move { // 在 format 宏中读取 count 的值使 resource 订阅 count 的变化 let response reqwest::get(format!(https://myserver.com/doubleme?count{count})).await.unwrap(); response.text().await.unwrap() }); // .state() 返回 SignalUseResourceState携带 resource 当前的状态信息 println!({:?}, double_count.state().read()); // UseResourceState::Pending // .read() 尝试获取上一次解析出的值 println!({:?}, double_count.read()); // None // 等待 resource 完成 std::thread::sleep(std::time::Duration::from_secs(1)); println!({:?}, double_count.state().read()); // UseResourceState::Done println!({:?}, double_count.read()); // Some(2) // 写入 countresource 将自动重跑 count 1; // count 现在是 2 std::thread::sleep(std::time::Duration::from_secs(1)); println!({:?}, double_count.state().read()); // UseResourceState::Done println!({:?}, double_count.read()); // Some(4)高频率写入旧任务被取消只保留最新结果再来看任务运行途中依赖被再次修改的场景resource 不会排队执行每一次请求而是取消当前 future、直接开启新任务# use dioxus::prelude::*; # let mut count use_signal(|| 3); # let double_count use_resource(move || async move { # let response reqwest::get(format!(https://myserver.com/doubleme?count{count})).await.unwrap(); # response.text().await.unwrap() # }); // 快速连续写入第一次写入触发重跑 count 1; // count 现在是 4 // 第二次写入会取消正在运行的任务并再次启动新任务 count 1; // count 现在是 5 // Stopped上一个 future 已被取消 println!({:?}, double_count.state().read()); // UseResourceState::Stopped // 值仍保留上一次解析结果而非清空 println!({:?}, double_count.read()); // Some(4) // 等待最终任务完成只得到最后一次 future 的结果 std::thread::sleep(std::time::Duration::from_secs(1)); println!({:?}, double_count.state().read()); // UseResourceState::Done println!({:?}, double_count.read()); // Some(8)值得注意的实现细节是与文档示例中展示的状态枚举不同当前源码 packages/hooks/src/use_resource.rs 中UseResourceState实际包含四种状态——Pendingfuture 仍在运行、Stoppedfuture 被强制停止、Pausedfuture 被临时暂停与Readyfuture 已完成。状态会被写入state这个内部 signal因此任何读取resource.state()的组件都能随之重新渲染。处理非响应式依赖use_reactive!use_resource能自动识别Signal、ReadSignal、Memo、Resource等一切响应式值作为依赖。但如果你需要在普通的 Rust 值变化时重跑 future例如组件 props 中的普通u32就需要用use_reactive!手动把它声明为依赖# use dioxus::prelude::*; # async fn sleep(delay: u32) {} #[component] fn Comp(count: u32) - Element { // 用 use_reactive 把 count 加入依赖列表count 变化时 resource 会重跑 let new_count use_resource(use_reactive!(|(count,)| async move { sleep(100).await; count 1 })); rsx! { {new_count:?} } } // 如果值本身已是响应式的就不需要手动调用 use_reactive #[component] fn ReactiveComp(count: ReadSignalu32) - Element { // count 是响应式值resource 在 count 变化时自动重跑 let new_count use_resource(move || async move { sleep(100).await; count() 1 }); rsx! { {new_count:?} } }从宏的实现packages/hooks/src/use_reactive.rs可以看到use_reactive!本质上会把参数展开为对use_reactive的调用(|count,| ...)变成use_reactive((count,), move |(count,)| ...)即把普通值放入引用元组、通过实现Dependencytrait 的比较来确定是否变化再将结果暴露成FnMut() - O闭包供use_resource使用。Dependencytrait 对所有不超过 8 个引用、且元素实现了PartialEq Clone的元组自动实现。它内部用use_signal缓存上一次的依赖输出并在组件每次运行时通过changed()判断是否需要更新。这一点在实际项目中非常实用。路由场景通常会给页面组件传入路径参数若参数声明为ReadSignali32Dioxus 会自动把传入的普通i32转成ReadSignalresource 就能跟随路由变化自动重新请求。可运行示例参见 examples/06-routing/router_resource.rs其中博客页面组件Blog(id: ReadSignali32)通过use_resource(move || future(id()))实现按文章 id 加载数据而对仅启动一次、不关心返回值的后台任务可参考 examples/05-using-async/future.rs 中use_future的用法。Resource 句柄完整生命周期控制 APIuse_resource返回的ResourceT是一个Copy Clone PartialEq的轻量句柄其内部仅持有若干个Signal、任务句柄与一个回调packages/hooks/src/use_resource.rs。除了文档中演示的直接读取它提供了丰富的方法用于精确控制异步任务方法行为影响resource.read()返回OptionT读取会订阅依赖值尚未就绪时为Noneresource.value()返回ReadSignalOptionT可传给其他 hook/组件订阅值变化resource.state()返回ReadSignalUseResourceState订阅当前任务状态resource.restart()取消当前任务并立即启动新任务状态重新变为Pendingresource.cancel()强制取消 future状态置为Stoppedresource.pause()临时暂停 future状态置为Pausedresource.resume()恢复被暂停的 future已结束时调用无效状态置为Pendingresource.clear()仅清空当前值不影响运行中的任务对应内部value.write().take()resource.finished()判断任务是否已结束Ready或Stopped使用peek()不会产生订阅resource.suspend()挂起渲染直到 future 就绪配合 Suspense 使用例如重新加载按钮可以这样实现onclick: move |_| resource.restart()。Resource还实现了Readable/Writabletrait因此在rsx!中可以直接书写{resource:?}之类的表达式并支持把它作为ReadSignalOptionT向下传递见FromResourceT for ReadSignalOptionT。特别的对于ResourceResultT, E还有专门的.result()方法可以将其拆成OptionResultMappedSignalT, MappedSignalE从而把成功值与错误值分离为两个独立的响应式信号。与 use_future、use_memo 的差异与选型Dioxus 提供了多个处理异步与派生状态的 hook选型时务必理解它们的边界use_future和use_resource一样会在组件中派生一个异步任务但它不返回 future 的结果只返回任务状态句柄UseFuture。它适合循环计时器、轮询、持续运行的后台任务这类不在乎产出值、只需启停控制的场景。其实现packages/hooks/src/use_future.rs中use_future仅在组件首次渲染时启动 future不会因依赖变化自动重跑需要手动restart()/cancel()控制。use_resource则额外返回结果并在依赖变化时自动重跑。use_memo与 resource 一样都基于已有状态派生产出值。但memo 会记忆闭包输出只有当依赖发生变化时才重算并且计算结果不变时不会触发订阅者的重新渲染而resource 不会记忆闭包输出——即使 future 的最终结果与上次完全相同只要 future 解析完成它也会让所有读取该值的地方重新运行。这是因为 resource 的语义是以异步任务完成作为刷新信号而非以值变化作为刷新信号。简单概括需要带返回值的异步请求 依赖驱动自动刷新选use_resource只需在后台跑一个不关心返回值的任务选use_future纯同步的派生值计算选use_memo。源码速览use_resource 是如何实现的use_resource的完整实现位于 packages/hooks/src/use_resource.rs其#[doc include_str!(../docs/use_resource.md)]说明本文档正是该 hook 的官方 API 文档。理解其内部结构有助于排查响应式失效等疑难问题核心流程如下用两个内部Signal保存状态value: SignalOptionT最新结果与state: SignalUseResourceState任务状态初始为Pending创建ReactiveContext并把其 change 通知保存在一个Cell中供后续依赖变化 → 重跑循环使用通过use_callback定义一个启动任务的回调先把状态置为Pending用rc.reset_and_run_in(mut future)在新依赖上下文中执行用户闭包得到 future再spawn包装任务使每个 poll 都运行在rc.run_in(...)的响应式作用域内从而自动收集依赖任务完成后写入state Ready与value Some(res)并waker.wake(())唤醒等待者最后用一个use_hook启动常驻循环changed.next().await等待依赖变化收到通知后先cancel()旧任务、再用回调启动新任务保证同一时刻只存在一个最新 future。依赖变化会取消旧任务这一机制是use_resource能避免请求风暴、天然具备防抖效果的关键设计而每个 poll 都发生在响应式作用域内则是它能自动追踪信号读取、实现声明式响应式的根基。注意事项由于Resource依赖对任务状态的自动订阅请勿通过resource.task()获取底层Task并直接修改它源码注释明确指出这会造成不一致的状态inconsistent state.finished()使用peek()读取状态、不产生订阅适合在事件处理等非渲染上下文中做一次性判断use_resource是标准 hook必须遵守 hooks 调用规则只能在组件或自定义 hook 的函数体顶层调用不能放在条件、循环或事件处理器内部否则会因调用顺序改变而破坏组件状态若组件在 future 尚未完成时被销毁任务会随作用域被回收需要跨页面保留数据时应把数据提升到更上层的组件或用全局状态管理。【免费下载链接】dioxusFullstack app framework for web, desktop, and mobile.项目地址: https://gitcode.com/GitHub_Trending/di/dioxus创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考