You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Svelte Store数组增删后出现空元素问题排查求助

SvelteKit计数器应用:nameList数组空元素问题根源与解决

问题描述

我有一个简单的SvelteKit 1.0应用,每当删除一个计数器后再添加新的计数器时,Svelte Store中的nameList数组会出现空元素。目前已通过过滤数组临时解决,但想明确问题根源。

相关代码

+page.svelte

<script lang="ts">
  import Counter from '../components/Counter.svelte';
  import { sum, nameList } from '../store';
  let counters = [Counter];
  let currentSum: number;
  let currentNameList: string[] = [];

  sum.subscribe((value) => {
    currentSum = value;
  });

  // 空元素会在这里被首次打印
  nameList.subscribe((value) => {
    console.log('nameList', value);
    currentNameList = value.filter(Boolean);
  });

  const addCounter = () => {
    counters = [...counters, Counter];
  };
</script>

<div class="flex m-auto flex-col max-w-sm">
  <h1 class="text-6xl text-center mb-6">Multiple Counter</h1>
  {#each counters as Counter, i}
    <Counter index={i} />
  {/each}
  <button
    on:click={addCounter}
    class="max-w-sm w-full mt-2 m-auto text-center
 bg-green-400 rounded text-white cursor-pointer"
  >
    new counter
  </button>
  <p class="flex ">title list: {currentNameList}</p>
  <p class="flex">sum of count: {currentSum}</p>
</div>

Counter.svelte

<script lang="ts">
  import { onMount, onDestroy } from 'svelte';
  import { sum, nameList } from '../store';
  export let index: number = 0;
  let count: number = 0;
  let name: string = 'new';
  let counterRef: HTMLDivElement;

  const increment = () => {
    sum.update((n) => n + 1);
    count++;
  };

  const decrement = () => {
    if (count > 0) {
      sum.update((n) => n - 1);
      count--;
    }
  };

  const reset = () => {
    sum.update((n) => n - count);
    count = 0;
  };

  const removeCounter = () => {
    sum.update((n) => n - count);
    counterRef.remove();
    nameList.update((n) => {
      let newList = [...n];
      newList.splice(index, 1);
      return newList;
    });
  };

  onMount(() => {
    nameList.update((n) => {
      let newList = [...n];
      newList[index] = name;
      return newList;
    });
  });

  onDestroy(() => {
    nameList.update((n) => {
      let newList = [...n];
      newList.splice(index, 1);
      return newList;
    });
  });

  const onChangeList = (event: Event) => {
    nameList.update((n) => {
      let newList = [...n];
      newList[index] = (event.target as HTMLInputElement).value;

      return newList;
    });
  };
</script>

<div
  class="max-w-sm bg-gray-100
    shadow-lg m-auto 
    flex relative 
    items-center mb-4 py-2"
  bind:this={counterRef}
>
  <input value={name} on:input={onChangeList} class="text-gray-600 mx-4 px-1 w-32" />
  <span class="text-lg font-bold px-4">{count}</span>
  <div class="ml-auto flex">
    <button on:click={increment} class="btn bg-red-500 rounded-l">+</button>
    <button on:click={decrement} class="btn bg-blue-500">-</button>
    <button on:click={reset} class="btn bg-yellow-500 rounded-r">0</button>
    <button
      on:click={removeCounter}
      class="bg-white text-gray-600 
        rounded-full h-8 w-8 mx-4
        flex items-center justify-center font-bold px-1 py-0">X</button
    >
  </div>
</div>

<style>
  .btn {
    --text-opacity: 1;
    color: #fff;
    color: rgba(255, 255, 255, var(--text-opacity));
    padding-top: 0.25rem;
    padding-bottom: 0.25rem;
    padding-left: 0.75rem;
    padding-right: 0.75rem;
    font-size: 1.125rem;
  }
</style>

问题根源

  1. 手动DOM操作破坏Svelte响应式管理:
    在removeCounter中直接调用counterRef.remove()删除DOM,但Svelte的{#each}循环并未感知到组件被移除,counters数组长度与实际存在的组件数量不一致。后续添加新组件时,{#each}分配的index与nameList实际长度不匹配,直接赋值newList[index] = name会跳过中间位置,导致空元素出现。

  2. 重复执行数组删除操作:
    removeCounter中已经对nameList执行了splice(index, 1),组件销毁时onDestroy钩子会再次执行相同的删除操作,相当于对nameList进行了两次删除。后续添加新组件时,索引对应的数组位置会出现空缺,表现为空元素。

  3. 依赖index作为组件标识不可靠:
    {#each}的index会随数组元素增删自动重新分配,删除中间计数器后,剩余组件的index会变化,但nameList结构已被修改,导致组件与数组元素的对应关系混乱。

修复方案

1. 让Svelte自动管理组件生命周期

不要手动删除DOM,通过修改counters数组触发组件的创建和销毁:

  • 在+page.svelte中添加删除函数并传递给Counter组件:
<script lang="ts">
  // ... 其他代码
  const removeCounter = (index: number) => {
    counters = counters.filter((_, i) => i !== index);
  };
</script>

{#each counters as Counter, i}
  <Counter index={i} onRemove={() => removeCounter(i)} />
{/each}
  • 修改Counter.svelte中的removeCounter,移除手动DOM操作和重复的nameList修改:
<script lang="ts">
  export let onRemove: () => void;
  // ... 其他代码
  const removeCounter = () => {
    sum.update((n) => n - count);
    onRemove();
  };

  // 移除onDestroy中的nameList.splice操作
</script>

2. 使用唯一ID代替index作为循环键

为每个计数器生成唯一ID,避免索引变化导致的对应关系错误:

// +page.svelte
<script lang="ts">
  let counters = [{ id: crypto.randomUUID(), component: Counter }];
  const addCounter = () => {
    counters = [...counters, { id: crypto.randomUUID(), component: Counter }];
  };
</script>

{#each counters as item, i}
  <Counter id={item.id} index={i} onRemove={() => removeCounter(i)} />
{/each}

3. 同步nameList与counters数组

确保nameList长度始终与counters一致,添加组件时直接push新元素而非通过索引赋值:

// Counter.svelte onMount
onMount(() => {
  nameList.update((n) => [...n, name]);
});

内容的提问来源于stack exchange,提问作者João Pedro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 02:25:48