diff --git a/src/pages/post_detail.rs b/src/pages/post_detail.rs index 9a5052f..30e8199 100644 --- a/src/pages/post_detail.rs +++ b/src/pages/post_detail.rs @@ -4,7 +4,18 @@ //! //! 数据获取:通过 `use_server_future` 调用 `get_post_by_slug` server function, //! 根据 URL 中的 slug 获取单篇文章详情(含正文 HTML、目录、封面及上下篇导航)。 -//! 路由参数 slug 变化时,Dioxus 会自动重跑该 future,无需镜像成 signal 再手动同步。 +//! +//! # 反应式取数的关键 +//! Dioxus 0.7 的 `use_server_future`(内部即 `use_resource`)只在闭包内读取的 +//! **signal** 变化时才会重跑 future——它通过 `ReactiveContext` 追踪闭包执行期间的 +//! 订阅。但本组件的 `slug` 是路由宏注入的普通 `String` prop,被 `move` 进闭包后 +//! 成了冻结快照,读取它不会建立订阅。因此上/下一篇导航(同一路由变体间的 slug +//! 变化)会复用组件实例、更新 props,却无法触发 future 重跑——表现为「URL 变了 +//! 但内容不变,刷新才生效」。 +//! +//! 修复:在闭包内通过 `router().current::()` 读取当前 slug。`current()` +//! 内部调用 `subscribe_to_current_context()`,在 `use_server_future` 的 +//! ReactiveContext 中注册订阅;路由变化时订阅触发,future 自动重跑。 //! 在 `wasm32` 目标下,server function 的函数体被替换为向服务端端点发起 HTTP POST 请求的客户端存根; //! 实际的数据库访问逻辑仅在 `feature = "server"` 启用时运行。 @@ -27,9 +38,22 @@ use crate::router::Route; /// 若文章不存在或加载失败,则展示对应的提示页面。 #[component] pub fn PostDetail(slug: String) -> Element { - // 直接读取 slug prop:路由参数变化时 Dioxus 会自动重跑该 server future, - // 无需镜像成 signal 再手动 set(render 期 set signal 是 Dioxus 反模式)。 - let post = use_server_future(move || get_post_by_slug(slug.clone()))?; + // 取得路由上下文句柄(不订阅组件层渲染,仅在闭包内按需订阅)。 + // 见模块文档:必须在闭包内读取路由状态才能建立反应式订阅,future 才会在 + // slug 变化(上/下一篇导航)时重跑。`slug` prop 本身是冻结的 String 快照, + // 不能作为依赖。 + let router = dioxus::router::router(); + + let post = use_server_future(move || { + // 在闭包内读取当前 slug:current() 内部会 subscribe_to_current_context(), + // 把订阅注册到 use_server_future 的 ReactiveContext,路由变化即重跑。 + let current_slug = match router.current::() { + Route::PostDetail { slug } => slug, + // 组件卸载/路由切走的瞬间可能命中其它变体,退回用 prop 值兜底。 + _ => slug.clone(), + }; + get_post_by_slug(current_slug) + })?; // 将结果映射为更直观的 Ok(post) / Err("not_found") / Err("error") 三种状态。 let post_data = post.read().as_ref().map(|r| match r {