
本文详解如何修复 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 的响应式设计哲学——让逻辑归逻辑,让视图归视图。










