fix(post): 修复上/下一篇导航后文章内容不更新
点击文章底部的上一篇/下一篇后,URL 正确变化为 /post/<new-slug>,但页面 内容仍停留在旧文章,只有刷新浏览器才生效。 根因:use_server_future(内部即 use_resource)依赖 ReactiveContext 追踪 闭包内读取的 signal 来决定重跑时机。但 PostDetail 的 slug 是路由宏注入的 普通 String prop,move 进闭包后成为冻结快照,读取它不建立任何反应式订阅。 同一 Route::PostDetail 变体间的导航(/post/a -> /post/b)会复用组件实例、 仅更新 props,而 use_server_future 的 task 只在首次 hook 时创建一次,因此 永远不会重跑。 此前 commit 225bb24 把 slug_signal 镜像 + render 期 set 改成直接读 prop, 消除了反模式,但也丢掉了唯一能触发重跑的订阅源(slug_signal 的读取), 反而引入了这个 bug。 修复:在闭包内通过 dioxus::router::router().current::<Route>() 读取当前 slug。current() 内部调用 subscribe_to_current_context(),在 use_server_future 的 ReactiveContext 中注册订阅,路由变化即触发重跑。render body 保持纯净。 验证(headless chromium,本地需设 APP_BASE_URL 绕过 CSRF 403): - 修复前:点 Next 后 URL 变 /post/1783580058,标题仍为旧文章(复现 bug) - 修复后:URL 变,标题随之变为新文章
This commit is contained in:
parent
6a3ac038da
commit
79978aa4a4
@ -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::<Route>()` 读取当前 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>() {
|
||||
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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user