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

Mer*_*ero 1 each binding bind 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}

Run Code Online (Sandbox Code Playgroud)

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

  • {#each}迭代次数比他应该做的多两倍
  • 通过单击绑定元素删除数组中的项目后 - svelte 引发错误“无法设置未定义的属性”

为什么它会这样工作?

Mer*_*ero 8

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

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

  • 有时{#each}块的工作效率是预期的两倍
  • 有时块绑定{#each}会抛出错误

所有代码均在 Svelte 编译器版本 3.59.1 和 4.0.5 中测试


问题 1 - 每个工作两次

我是什么意思?

  • 每个块使所有迭代两次

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

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

{#each foodList as {icon}}
  <div> {icon} </div>
{/each}
Run Code Online (Sandbox Code Playgroud)

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

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

这是相同的代码,只是将 prop 'elem' 添加到内部每个 div 绑定的对象中{#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}
Run Code Online (Sandbox Code Playgroud)

是的...我们进行了6 次迭代,还有两次

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

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

发生这种情况是因为我们使用相同的数组进行{#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}
Run Code Online (Sandbox Code Playgroud)

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


问题 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}
Run Code Online (Sandbox Code Playgroud)

我们预计,如果我们单击div- 数组项,它所绑定的数组项将被切片,并且我们将在屏幕上丢失它们。正确...但是我们得到了错误,因为{#each}block make仍然进行了 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}
Run Code Online (Sandbox Code Playgroud)

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}
Run Code Online (Sandbox Code Playgroud)

看起来有 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}

Run Code Online (Sandbox Code Playgroud)

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

最后一站

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

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

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

我们将很高兴您的指正,再见,不要让胜利。

聚苯乙烯

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