From b5a64f8dcf634b2aca01864d1bc18313625957f1 Mon Sep 17 00:00:00 2001 From: xfy Date: Thu, 9 Jul 2026 17:12:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(post):=20=E4=B8=8A=E4=B8=8B=E7=AF=87?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E5=90=8E=E5=8F=AF=E8=BF=90=E8=A1=8C=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=9D=97=E6=B6=88=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 承接前两轮修复:路由重跑(79978aa)、正文更新(f86cb48)后,翻页时文章正文 正确切换了,但可运行代码块(CodeRunner)消失——容器 div 还在,CodeMirror 编辑器没挂载。 根因:上下篇切换(/post/rust → /post/go)时 PostContent 组件被复用(仅重渲染, 非重新挂载)。其内部 CodeRunner 用片段索引作 key(runner-{i}),而两篇文章的 代码块索引恰好相同(都是 1/3/5),keyed diff 按相同 key 复用 CodeRunner 实例。 复用的实例保留 use_hook/use_effect 状态:挂载 use_effect 的「防重复 init」守卫 (editor_handle.is_some())阻止 CodeMirror 挂载到新的(已替换的)DOM 容器, 而旧 CodeMirror 还绑定在被销毁的 rust DOM 上。同时 PostContent 自身的 use_effect (__initPostContent 复制按钮 / 灯箱初始化)也不重跑,新文章交互脚本不初始化。 曾尝试直接给 PostContent 加 key=post.slug,无效:Dioxus 的 key diff (diff_keyed_children)只在「兄弟节点列表」里生效,对单个非列表元素的 key 变化走 diff_non_keyed 路径、按位置复用,不触发 remount(经 use_hook 探针确认: 翻页后 CodeRunner 函数体执行了但 use_hook 从未重新执行)。 修复:把 PostContent 包进单元素 keyed 列表(for + iter::once + key=slug), 使其进入 keyed diff 路径——slug 变化时旧实例移除、新实例创建,连带 CodeRunner 重新挂载、交互脚本重新初始化。 验证(headless chromium,rust↔go 各含3个代码块): - 修复前:翻页后容器在、CodeMirror 挂载数=0(红灯) - 修复后:CodeMirror 挂载数=3,骨架屏清零;prev/next 双向 + SSR 首屏均正常 --- src/pages/post_detail.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/pages/post_detail.rs b/src/pages/post_detail.rs index 30e8199..8c65c05 100644 --- a/src/pages/post_detail.rs +++ b/src/pages/post_detail.rs @@ -78,7 +78,27 @@ pub fn PostDetail(slug: String) -> Element { PostToc { toc_html: toc.clone() } } - PostContent { content_html: post.content_html.clone().unwrap_or_default() } + // 用单元素 keyed 列表包裹 PostContent,key 绑定 slug。 + // + // 为什么不能直接给 PostContent 加 key:Dioxus 的 key diff(diff_keyed_children) + // 只在「兄弟节点列表」里生效,对单个非列表元素的 key 变化会走 diff_non_keyed + // 路径、按位置复用,不触发 remount。把组件放进单元素 for 循环并带 key, + // 才会进入 keyed diff——slug 变化时旧 PostContent 被移除、新的被创建。 + // + // 为什么需要 remount:上下篇切换时 PostContent 若被复用(仅重渲染), + // (1) 内部 CodeRunner 用片段索引作 key,两篇文章索引可能相同(如 1/3/5), + // keyed diff 按相同 key 复用 CodeRunner 实例——其挂载 use_effect 的 + // 「防重复 init」守卫阻止 CodeMirror 挂载到新的(已替换的)DOM 容器, + // 表现为翻页后代码块消失; + // (2) PostContent 自身的 use_effect(__initPostContent 复制按钮 / 灯箱初始化) + // 也不会重跑,新文章的交互脚本不初始化。 + // 强制 remount 让编辑器与脚本随文章切换全部重新初始化。 + for post_slug in std::iter::once(post.slug.clone()) { + PostContent { + key: "{post_slug}", + content_html: post.content_html.clone().unwrap_or_default(), + } + } PostFooter { post: post.clone() }