fix(post): 上下篇切换后可运行代码块消失

承接前两轮修复:路由重跑(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 首屏均正常
This commit is contained in:
xfy 2026-07-09 17:12:48 +08:00
parent 2498cd9c0b
commit b5a64f8dcf

View File

@ -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 列表包裹 PostContentkey 绑定 slug。
//
// 为什么不能直接给 PostContent 加 keyDioxus 的 key diffdiff_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() }