首页 > 解决方案 > Svelte 每个工作两次/每个绑定错误(无法设置未定义的属性)

问题描述

我正在尝试在{#each}块中绑定元素并通过单击将它们删除。

    <script>
        const foodList = [
          { icon: '', elem: null, },
          { icon: '', elem: null, },
          { icon: '', elem: null, },
        ]; 
        const remove = (index) => { 
          foodList.splice(index, 1);
          foodList = foodList;
        };
    </script>

    {#each foodList as {icon, elem}, index}
      <div 
        bind:this={elems[index]}
        on:click={remove}
      >
        {icon}
      </div>
    {/each}

在我的代码中,我遇到了 2 个问题:

为什么它会这样工作?

标签: bindingbindeachsvelte

解决方案


我写这篇文章不是为了寻求帮助,而是为了那些会遇到同样问题的人

这两个问题的根源相同,所以我在这里收集它们:

  • 有时{#each}块的工作量是预期的两倍
  • 有时{#each}绑定会引发错误

在 Svelte 编译器版本 3.44.1 中测试的所有代码


问题 1 - 每个工作两次

我是什么意思?

  • 每个块进行所有迭代两次

让我告诉你,看看下面的代码,多少次迭代{#each}会做?

<script>
  const foodList = [
        { icon: '' },
        { icon: '' },
        { icon: '' } 
  ]; 
</script>

{#each foodList as {icon}}
  <div> {icon} </div>
{/each}

如果您的答案是 - 3,那么恭喜您,您是对的。

好的,现在我们需要绑定元素,我们正在{#each}块中渲染。

这是相同的代码,只是为里面的每个 div 绑定的对象添加了 prop 'elem'{#each}

看看下面再试一次,会有多少次迭代{#each}

<script>
  const foodList = [
        { icon: '', elem: null, },
        { icon: '', elem: null, },
        { icon: '', elem: null, } 
  ]; 
</script>

{#each foodList as {icon, elem} }
  <div 
    bind:this={elem}
   > 
     {icon} 
   </div>
{/each}

对...我们进行了6次迭代,再增加两次

您可以通过添加一些console.log()at first{#each}块代码来查看它,如下所示:

{#each foodList as {icon, elem}, index}
  {console.log('each iteration: ', index, icon) ? '' : ''}
  <div 
    bind:this={elem}
   > 
     {icon} 
   </div>
{/each}

发生这种情况是因为我们使用相同的数组进行{#each}迭代和绑定

如果我们将为绑定创建新数组-问题就会消失

<script>
  const foodList = [
        { icon: '' },
        { icon: '' },
        { icon: '' } 
  ]; 
  const elems = [];
</script>

{#each foodList as {icon}, index }
  { console.log('each iteration: ', index, icon) ? '' : ''}
  <div 
    bind:this={elems[index]} 
   > 
     {icon} 
   </div>
{/each}

是的......现在问题消失了,我们得到了3 次迭代,正如我们预期的那样。
这是一个存在很长时间的错误,正如我试图找出的那样 - 至少 1 年。随着输出代码变得更加复杂,这在不同的情况下会导致不同的问题。


问题 2 - 对可迭代数组进行切片后,每个绑定都会引发错误

(类似于:'无法设置未定义的属性')

我是什么意思?

  • 在我们删除一个可迭代数组项后,每个块绑定都会引发错误

相同的示例,只是在其元素单击上添加了删除数组项:

<script>
    const foodList = [
        { icon: '', elem: null, },
        { icon: '', elem: null, },
        { icon: '', elem: null, }  
    ]; 
    
    const remove = (index) => { 
        foodList.splice(index, 1);
        foodList = foodList;
    };
</script>

{#each foodList as {icon, elem}, index} 
    {console.log('each iteration: ', index, icon) ? '' : ''} 
    <div 
        bind:this={elem}
        on:click={remove}
    >
        {icon}
    </div>
{/each}

我们希望,如果我们单击div- 数组项,它所绑定的将被切片,我们将在屏幕上丢失它们。正确...但是我们得到了错误,因为{#each}仍然是 3 次迭代,而不是我们等待的 2 次。

再次为清楚起见,我们的代码步骤:foodList 长度为 3 -> 我们点击食物图标 -> foodList 长度为 2(我们用点击的图标剪切项目)。

在这之后{#each}应该做 2 次迭代来渲染 foodList 中的每个左侧图标,但他做了 3 次!

这就是我们遇到问题的原因,我们的代码试图将新属性写入未定义(项目被切片,因此当我们尝试读取\写入它时 - 存在未定义。

// foodList [ { icon: '', elem: null, }, { icon: '', elem: null, }]
foodList[2].elem = <div>; // "不能设置未定义的属性"

这是一个错误,如果我们使用相同的数组进行{#each}迭代和绑定,就会发生这种情况。

对我的问题最干净的解决方法是将可迭代和绑定数据分离到不同的数组中:

<script>
    const foodList = [
        { icon: '' },
        { icon: '' },
        { icon: '' }  
    ]; 
    let elems = [];
    
    const remove = (index) => { 
        foodList.splice(index, 1);
        foodList = foodList;
    };
      
    
</script>

{#each foodList as {icon, cmp}, index} 
    {console.log('each iteration: ', index, icon) ? '' : ''} 
    <div 
        bind:this={elems[index]}
        on:click={remove}
    >
        {icon}
    </div>
{/each}

但是......让我们通过添加来看看新elems数组$: console.log(elems);

elems(这是响应式表达式,每次更改时都会打印数组)

<script>
    const foodList = [
        { icon: '' },
        { icon: '' },
        { icon: '' }  
    ]; 
    let elems = [];
    
    const remove = (index) => { 
        foodList.splice(index, 1);
        foodList = foodList;
    };
      
    $: console.log(elems);  
</script>

{#each foodList as {icon, cmp}, index} 
    {console.log('each iteration: ', index, icon) ? '' : ''} 
    <div 
        bind:this={elems[index]}
        on:click={remove}
    >
        {icon}
    </div>
{/each}

看起来有 2 条消息要告诉你

  • 我们没有错误
  • 我们在数组中有新null项目elems

这意味着问题仍然存在({#each}块仍然为切片项目进行 1 次额外迭代)。

现在我们可以elems在 foodList 切片后过滤数组,只要在页面更新后进行,例如tick().

完整代码:

<script>
    import { tick } from 'svelte';
    
    const foodList = [
        { icon: '', elem: null, },
        { icon: '', elem: null, },
        { icon: '', elem: null, } 
    ]; 
    let elems = [];
    
    const remove = async (index) => { 
        foodList.splice(index, 1);
        foodList = foodList;
        await tick();
        elems = elems.filter((elem) => (elem !== null)); 
    };
    
    $: console.log(elems);  
</script>

{#each foodList as {icon, elem}, index}
    {console.log('each iteration: ', index, icon) ? '' : ''}
    <div 
        bind:this={elems[index]}
        on:click={remove}
    >
        {icon}
  </div>
{/each}

请记住{#each}块仍然可以额外工作 1 次,并且我们将 null 作为绑定元素,我们只是在页面更新后对其进行过滤。

最后一站

不知道该说什么真的......我在这个****上浪费了太多时间,试图弄清楚为什么我的代码不能正常工作。

我喜欢苗条,但我不喜欢虫子

我真的希望这个小指南能帮助你们中的一些人节省很多时间。

很高兴您的更正,再见,不要让胜利。

附言

是的,这需要时间,但是......永远不知道什么时候需要帮助,分享你的知识


推荐阅读