【译】索引作为键是一种反模式(Index as a key is an anti-pattern)

简介: 【译】索引作为键是一种反模式(Index as a key is an anti-pattern)

写在前头

原文地址

我多次看到开发人员在呈现列表时使用项的索引作为键。

{
    todos.map((todo, index) => (
        <Todo {...todo} key={index} />
    ));
} 
复制代码


它看起来很优雅,而且去掉了警告(这才是“真正的”问题,对吧?)这里的危险是什么?


它可能会破坏你的应用程序和显示错误的数据!

让我解释一下,键是React用来识别DOM元素的唯一工具。如果您将一个项目推入列表或删除中间的内容会发生什么?如果键与之前相同,React假定DOM元素表示与之前相同的组件。但这种情况已不复存在。

微信截图_20221112165314.png


为了演示潜在的危险,我创建了一个简单的示例(带有源代码)

微信截图_20221112165329.png


示例的截图,显示了使用索引作为键的危险。

结果是,当不传递任何东西时,React使用索引作为键,因为它是目前最好的猜测。此外,它还会警告你,这是次优的(它说的话有点令人困惑,是的)。如果你自己提供它(键),React会认为你知道你在做什么,记住这个例子,会导致不可预测的结果。


Better

每一个这样的项目都应该有一个永久的和唯一的属性。理想情况下,应该在创建项时赋值。当然,我说的是一个id。然后我们可以用下面的方法来使用它:

{
    todos.map((todo) => (
        <Todo {...todo} key={todo.id} />
    ));
}
复制代码


注意:首先查看项目的现有属性。有可能他们已经有了可以用作身份证明的东西。

一种方法就是抽象地把编号往上移一步。使用全局索引(global index)可以确保任何两个项目具有不同的id。

let todoCounter = 1;
const createNewTodo = (text) => ({
    completed: false,
    id: todoCounter++,
    text
}
复制代码


Much better

生产解决方案应该使用更健壮的方法来处理项的分布式创建。因此,我推荐nanoid。它快速生成短的非顺序的url友好的唯一id。代码可能如下所示:

import { nanoid } from 'nanoid';
const createNewTodo = (text) => ({
    completed: false,
    id: nanoid(),
    text
}
复制代码


注意:

nanoid文档声明它不应该与React一起使用。他们提到在渲染过程中使用它,这是绝对正确的。在这里,我使用它创建一个稳定的ID。所以不要担心,我们谈论不同的事情,这个建议仍然有效。

TL;DR:为每个项目生成一个唯一的id,并在呈现列表时使用它作为键。


Update: Exception from the rule

很多人问他们是否总是要生成id。其他人提出了一些用例,其中使用索引作为键似乎是合理的。

的确,有时生成新的id是多余的,可以避免。例如,翻译许可条款或贡献者名单。

为了帮助你做出决定,我将这些例子的三个共同点放在一起:

  1. 列表和项是静态的—它们不计算也不更改;
  2. 列表中的项目没有id;
  3. 列表从不重新排序或过滤。

当满足所有这些条件时,您可以安全地将索引用作键。


Update 2: React, Preact, and *react

尽管在这篇文章中我写的是React,但问题并不仅限于它。在类似的库中,比如Preact,危险也存在。然而,效果可能是不同的。

请参阅下面的StackOverflow问题,其中最后一个元素消失了。也请注意Preact的创建者提供的答案中的解释,Jason Miller

Wrong components rendered by Preact


Update 3: nanoid

之前本指南推荐的是shortid.。这个ID生成器被弃用了,所以我用nanoid.代替了它。


Update 4: Clarify nanoid + React usage

正如一些人评论的那样,nanoid不建议直接在React的组件渲染中使用。读完这篇文章,我希望你知道为什么。我添加了一个注释,澄清本文中的建议仍然有效。


参考资料及相关文章


如果你喜欢这篇文章,请不要忘记在下方留言👏。每次鼓掌通知对我来说都是一种激励。 如果你想了解更多,我开设了一个关于JavaScript的YouTube频道,请考虑订阅。从一开始就陪伴我,帮助我变得更好。



目录
相关文章
|
4月前
|
PHP 数据库
laravel表单验证的exists、unique去除软删除字段的校验
虽然Laravel的验证系统非常强大和灵活,但在处理软删除数据时仍需要特别注意。通过正确使用验证规则,并在需要时创建自定义验证规则,你可以确保应用的数据验证既准确又高效。记得在对 `unique`和 `exists`规则进程自定义时,清晰地注明你排除软删除记录的意图,这将使得代码更容易理解和维护。
146 4
|
6月前
|
Java 开发者
JDK 21中的记录模式(Record Patterns):简化对象匹配与解构
本文将详细介绍JDK 21中引入的新特性——记录模式(Record Patterns)。记录模式是一种强大的语言特性,它允许开发者在switch表达式中使用简化的语法来匹配和解构记录类型(record types)。本文将解释记录模式的概念、语法、使用场景以及与传统模式匹配的区别,并通过示例代码展示记录模式在实际开发中的应用。
|
存储 算法 关系型数据库
explain中key_len的作用
还在等什么,快来一起讨论关注吧,公众号【八点半技术站】,欢迎加入社群
explain中key_len的作用
重构——57字段上移(Pull Up Field)
字段上移(Pull Up Field):两个子类拥有相同的字段;将该字段移至超类
1626 0
|
算法 JavaScript 前端开发
简单通透理解: 为什么不建议使用 index 作为 key 值
云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 今天探讨一个我们前端面试中经常会遇到的一个问题。 使用 index 作为 key 值有什么问题呢? 在我们日常开发中我们经常会和 key 值打交道。
简单通透理解: 为什么不建议使用 index 作为 key 值
|
SQL Go
【SQL】ROW_NUMBER() OVER(partition by 分组列 order by 排序列)用法详解+经典实例
【SQL】ROW_NUMBER() OVER(partition by 分组列 order by 排序列)用法详解+经典实例目录 0、填充数据1、使用row_number()函数对订单进行编号,按照订单时间倒序。
13122 0