Svelte 每个工作两次/每个绑定错误(无法设置未定义的属性)

Svelte each worked twice / each binding error ( Cannot set properties of undefined )

我正在尝试绑定 {#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 个问题:

为什么会这样?

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

这两个问题同根同源,特此汇总:

  • 有时 {#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} 块中渲染。

这是相同的代码,只是在 {#each}

中为每个 div 绑定的对象添加了属性 'elem'

往下看,再试一次,要迭代多少次{#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 次迭代,两次 次。

首先在{#each}块代码中加一些console.log()就可以看到,像这样:

{#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 - 在可迭代数组被切片后每个绑定都会抛出错误

(类似于:'Cannot set properties of undefined')

我是什么意思?

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

同样的例子,只是添加了删除元素上的数组项点击:

<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 次!

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

// foodList [ { icon: '', elem: null, }, { icon: '', elem: null, }]
foodList[2].elem = <div>; // "Cannot set properties of undefined"

这是一个错误,如果我们使用相同的数组进行{#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}

但是...让我们通过添加 $: console.log(elems);

来查看新的 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 个消息要告诉你

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

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

目前我们可以在foodList切片后过滤elems数组,只需在页面更新后进行即可,例如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 作为绑定元素,我们只是在页面更新后过滤它。

最后一战

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

喜欢苗条,不喜欢虫子

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

将很高兴您的更正,看到你,不要让赢。

P.S.

是的,这需要时间,但是...永远不知道您何时需要帮助,分享您的知识