
本文详解如何修复 Svelte 中因嵌套 {#each} 导致的相关文章重复渲染问题,通过预过滤数据替代模板内多层条件判断,确保每篇相关文章仅显示一次。
本文详解如何修复 svelte 中因嵌套 `{#each}` 导致的相关文章重复渲染问题,通过预过滤数据替代模板内多层条件判断,确保每篇相关文章仅显示一次。
在构建博客类 Svelte 应用时,“相关文章”组件常需根据当前文章的标签(tags)匹配其他已发布文章。但若直接在模板中使用嵌套循环(如外层遍历所有文章、内层遍历其标签),极易引发重复渲染——例如,当前文章含 3 个标签,而另一篇候选文章恰好匹配其中任意一个,该候选文章就会被渲染 3 次(每个匹配标签触发一次
原始代码的问题根源在于逻辑耦合在模板层:
{#each posts as post} {#each post.meta.tags as tag} {#if currentPostTags.includes(tag)} <!-- 匹配即渲染 → 同一篇文章可能被多次插入 --> <li>...</li> {/if} {/each} {/each}
这种写法违背了“数据驱动视图”的原则:模板应负责呈现,而非筛选与去重。
✅ 正确解法是将匹配与去重逻辑前置到 JavaScript 层,在组件加载后一次性生成干净的 relatedPosts 数组:
<script> import { getMarkdownPosts } from '$lib/utils/getPosts'; import { onMount } from 'svelte'; let relatedPosts = []; export let currentPostTitle, currentPostTags; onMount(async () => { const allPosts = await getMarkdownPosts(); // 关键:单次过滤,语义清晰且天然去重 relatedPosts = allPosts.Filter(post => { const { title, tags, published } = post.meta; // 排除自身 + 未发布文章 + 无标签交集的文章 return ( title !== currentPostTitle && published && currentPostTags.some(tag => tags.includes(tag)) ); }); }); </script> {#if relatedPosts.length > 0} <h3>Related posts</h3> <ul> {#each relatedPosts as { slug, meta: { title } }} <li><a href="/blog/{slug}"><h4>{title}</h4></a></li> {/each} </ul> {:else} <p>No related posts found.</p> {/if}
? 关键改进说明:
- ✅ 逻辑分离:filterRelatedPosts() 将业务规则(排除自身、检查发布时间、计算标签交集)集中处理,模板仅做扁平化渲染;
- ✅ 天然去重:filter() 对每篇文章只执行一次判断,匹配即保留一项,彻底避免重复
- ;
- ✅ 性能更优:避免模板中重复计算 currentPostTags.includes(tag),尤其当 tags 数组较大时;
- ✅ 可维护性强:后续如需增加权重排序(如按共同标签数降序)、限制数量(.slice(0, 3))或添加缓存,均只需修改 js 层逻辑。
⚠️ 注意事项:
- 若 currentPostTags 是响应式变量(如由 $: 声明),需改用 $: 响应式声明或 subscribe 监听变化,否则 onMount 中的快照无法自动更新;
- 标签匹配默认区分大小写,如需忽略大小写,可统一转为小写再比较:currentPostTags.map(t => t.toLowerCase()).includes(tag.toLowerCase());
- 生产环境建议对 getMarkdownPosts() 添加错误处理,避免白屏。
通过将数据筛选从模板移至脚本层,你不仅解决了重复渲染问题,更让组件结构更符合 Svelte 的响应式设计哲学——让逻辑归逻辑,让视图归视图。