无需 ID 即可同步 DOM 状态和数据

问题:管理动态列表中的状态 DOM 元素

保持**有状态的 DOM 元素**与可变数据列表同步是一项看似简单但很快就会变成代码混乱的挑战。这个问题几乎存在于每个主要的 JavaScript 框架中 - 无论您使用的是**React**、**Vue**还是**Svelte**。

让我们来设定场景

想象一下:

您有一个标签列表,并希望为每个标签呈现一个**表单输入**。这看起来很容易,对吧?但现在,如果这些标签没有唯一标识符怎么办。

一开始,你可能会想:

“我只会使用标签本身作为钥匙。”

但当标签不能保证唯一时,这种方法很快就会失败。

错误的方法:使用数组索引作为键

您可以尝试使用**数组索引**作为键:

{rows.map((row, i) => (
    
        
        
    
))}

虽然这可以消除即时错误,但像 (和其他) 这样的 linting 工具会因此而在您的终端中填满错误 — — 这是有原因的!使用索引作为键可能会导致 **性能不佳**,并在列表发生变化时(例如,添加或删除项目时)导致 **状态错位**。

提示

了解为什么 JavaScript 框架需要键来呈现数组。

了解为什么 SolidJS 是个例外

那么完全管理状态怎么样?

你可能会想到另一种选择:

当然,你可以尝试。但这意味着:

  • 每次更改时跨多个组件的状态进行混洗。
  • 编写复杂的处理程序来管理每个输入交互。
  • 运行一小段 JavaScript 只是为了保持同步。
  • 如果有更简单的方法呢?

    如果我告诉您有一种**更简单**、**更快捷**的方法来避免这种困境,您会怎么想?

    让我告诉你如何停止对抗框架的渲染逻辑。

    解决方案:比您想象的更简单

    只需从这里更改您的状态

    const [rows, setRows] = useState(state);

    对此

    const [rows, setRows] = useState<[Row, number][]>(
      state.map((row, key) => [row, key]),
    );

    就是这样!

    现在,您不再拥有**不稳定的索引**,而是拥有与您的数据完美打包的**稳定键**。

    {rows.map(([row, key]) => (
        
            
            
        
    ))}

    示例:实际用法

    你的 `remove` 和 `append` 函数可能如下所示

    const remove = (key: number) =>
      setRows((state) => state.filter((row) => row[1] !== key));
    const append = () =>
      setRows((state) => [...state, [fourth, (state.at(-1)?.[1] ?? -1) + 1]]);
    
    
    {rows.map(([row, key]) => (
      
        
        
        
      
    ))}
    

    在 Svelte

    {#each rows as [row, key] (key)}
      
      
    {/each}

    在 Vue 中

    结论

    这就是您可能在前端项目中使用的小 JS 技巧。

    欢迎在评论中分享想法和问题