Svelte组件中目录重复问题排查及解决方案
问题:切换页面后右侧悬浮目录未更新为当前文章内容
当前Svelte组件实现了滚动时在右侧窗口展示文章目录的功能,但切换到另一篇带独立目录的文章时,右侧窗口仍显示上一篇的目录。目录由remark-toc自动生成无法修改。
原因分析
- 组件仅在
onMount时获取一次目录内容,单页应用路由切换后组件不会重新挂载,导致tableOfContents变量保留旧值 - Intersection Observer未在页面切换时销毁并重新监听新目录元素,可能造成
showAside状态异常
解决方案
1. 适配SvelteKit的修复代码
<script> // @ts-nocheck import { onMount, onDestroy } from 'svelte' import { scale, fade } from 'svelte/transition' import { page } from '$app/stores' let tableOfContents = null let showToc = false let showAside = false let observer = null function getTableOfContents() { const postTableOfContentsEl = document.querySelector('#в-этой-статье + ol') if (postTableOfContentsEl) { tableOfContents = postTableOfContentsEl.outerHTML } else { tableOfContents = null } } function setupObserver() { // 清理旧的Observer if (observer) { const oldTarget = document.querySelector('#в-этой-статье + ol') oldTarget && observer.unobserve(oldTarget) observer.disconnect() } const targetEl = document.querySelector('#в-этой-статье + ol') if (!targetEl) { showAside = false return } observer = new IntersectionObserver(([entry]) => { showAside = entry.boundingClientRect.bottom < 0 }) observer.observe(targetEl) } onMount(() => { setupObserver() getTableOfContents() // 监听路由变化 const unsubscribe = page.subscribe(() => { // 等待DOM渲染完成后更新 setTimeout(() => { getTableOfContents() setupObserver() showToc = false }, 100) }) return unsubscribe }) onDestroy(() => { // 组件销毁时清理资源 if (observer) { const targetEl = document.querySelector('#в-этой-статье + ol') targetEl && observer.unobserve(targetEl) observer.disconnect() } }) function toggleToc() { showToc = !showToc } </script> {#if tableOfContents} {#if showAside} <div in:fade={{ duration: 200 }} out:fade={{ duration: 200}}> {#if showToc} <div class="toc" class:active={showToc} transition:scale={{ start: 0, duration: 300, delay: 0 }}> <p class="toc-title">В этой статье</p> {@html tableOfContents} </div> {/if} </div> <button on:click={toggleToc} in:fade={{ duration: 200 }} out:fade={{ duration: 200}}> <svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" class="cf-icon-svg" viewBox="-2.5 0 19 19"><path d="M2.85 4.836a1.03 1.03 0 0 1-1.029 1.03h-.003a1.03 1.03 0 1 1 0-2.059h.003a1.03 1.03 0 0 1 1.03 1.03zm0 4.346a1.03 1.03 0 0 1-1.029 1.03h-.003a1.03 1.03 0 1 1 0-2.059h.003a1.03 1.03 0 0 1 1.03 1.03zm0 4.346a1.03 1.03 0 0 1-1.029 1.03h-.003a1.03 1.03 0 1 1 0-2.059h.003a1.03 1.03 0 0 1 1.03 1.03zm1.13-8.692a1.03 1.03 0 0 1 1.029-1.029h7.173a1.03 1.03 0 1 1 0 2.058H5.009a1.03 1.03 0 0 1-1.03-1.029zm9.231 4.347a1.03 1.03 0 0 1-1.029 1.029H5.009a1.03 1.03 0 1 1 0-2.059h7.173a1.03 1.03 0 0 1 1.03 1.03zm0 4.345a1.03 1.03 0 0 1-1.029 1.03H5.009a1.03 1.03 0 1 1 0-2.059h7.173a1.03 1.03 0 0 1 1.03 1.03z"/></svg> </button> {/if} {/if}
2. 关键修改说明
- 监听路由变化事件,触发目录重新获取和Observer重建
- 每次路由切换后清理旧Observer,避免监听失效元素
- 切换页面后默认关闭目录弹窗,优化用户体验
- 在组件销毁时清理Observer,防止内存泄漏
3. 非SvelteKit项目适配方案
如果使用自定义路由或其他路由库,替换路由监听部分代码:
// 监听浏览器popstate事件 const handleRouteChange = () => { setTimeout(() => { getTableOfContents() setupObserver() showToc = false }, 100) } window.addEventListener('popstate', handleRouteChange) // 组件销毁时移除监听 onDestroy(() => { window.removeEventListener('popstate', handleRouteChange) // 清理Observer逻辑... })
内容的提问来源于stack exchange,提问作者Лео
相关产品推荐
相关产品推荐

