Svelte递归组件<svelte:self>标签无法正常触发重渲染问题
Svelte 递归组件
<svelte:self>更新不触发重渲染问题 问题表现
使用<svelte:self />实现类文件浏览器的递归渲染逻辑时,交互存在异常:首次点击任意标签渲染出对应子层级内容后,后续点击其他同层标签不会切换子内容,页面始终停留在首次渲染的子内容结果。
预期交互逻辑:点击general settings时展示其下的test、abc子标签,点击orders时切换展示ladida、haha子标签。
实际交互结果:首次渲染子内容后,始终展示test、abc标签,点击操作无法触发内容切换。
涉及代码
父组件 Parent.svelte
作为递归组件外层容器,负责定义组件尺寸、传入基础渲染数据:
<script> import RecursiveChild from 'RecursiveChild.svelte'; const data: any = [ { label: 'general settings', options: [ { label: 'test' }, { label: 'abc' }, ], }, { label: 'orders', options: [ { label: 'ladida' }, { label: 'haha' }, ], }, ]; </script> <div class="main"> <RecursiveChild paths={data} /> </div> <style lang="scss"> @import '../lib.scss'; .main { @include fill(); display: flex; } </style>
递归子组件 RecursiveChild.svelte
使用<svelte:self />实现递归渲染,为异常发生组件:
<script> export let paths; let labels = paths ? paths.map((value) => value.label) : []; let pressedLabel: number; function handleClick(i: number) { pressedLabel = i; } </script> <div class="paths"> {#each labels as label, i} <div class="label" on:click={() => handleClick(i)}>{label}</div> {/each} </div> {#if pressedLabel !== undefined} <svelte:self paths={paths[pressedLabel].options} /> {/if} <style lang="scss"> .paths { display: inline-flex; flex-direction: column; } .label { margin: 25px; background-color: green; } </style>
故障原因
两个问题共同导致渲染异常:
labels为组件初始化时一次性计算的普通变量,未做响应式声明,当组件接收到的pathsprop变更时,labels不会重新计算。- 递归渲染的
<svelte:self>未绑定唯一key,选中项切换时Svelte会复用已存在的子组件实例,不会触发组件重新初始化,导致子组件保留旧状态、旧渲染结果。
修复方法
- 将
labels改为响应式声明,保证paths变更时自动重新计算标签列表。 - 给
<svelte:self>绑定当前选中索引作为key,选中项变化时强制子组件重新渲染,避免DOM复用导致的状态残留。
修复后RecursiveChild.svelte核心代码:
<script> export let paths; // 响应式计算标签列表 $: labels = paths ? paths.map((value) => value.label) : []; let pressedLabel: number; function handleClick(i: number) { pressedLabel = i; } </script> <div class="paths"> {#each labels as label, i} <div class="label" on:click={() => handleClick(i)}>{label}</div> {/each} </div> {#if pressedLabel !== undefined} <!-- 绑定key避免组件复用导致的状态残留 --> <svelte:self key={pressedLabel} paths={paths[pressedLabel].options} /> {/if} <style lang="scss"> .paths { display: inline-flex; flex-direction: column; } .label { margin: 25px; background-color: green; cursor: pointer; } </style>
内容的提问来源于stack exchange,提问作者Lukas
相关产品推荐
相关产品推荐

