如何找到“警告:列表中的每个子项都应该有一个唯一的“key”属性”的原因?

16

我经常遇到这个错误

Warning: Each child in a list should have a unique "key" prop.  Check the render method of `MyComponent`.

在 React 中,错误消息总是告诉你有问题的组件,但没有告诉你具体有问题的 HTML 标签 / 虚拟 DOM 元素。在一个大型代码库中工作时,有时会有很大的组件,这使得找到错误的源头非常困难。

是什么导致了这个错误?我正在寻找一个权威的列表。

  • 在数组中缺少“ key”属性的标签(非常确定)
  • 两个具有相同“ key”属性值的标签?(我认为对此有不同的错误消息)

连续书写的两个元素(例如 <div></div><div></div>)被算作“列表中的子项”吗?它们也会引起错误吗?

有哪些有效的查找问题标签的策略?

  • 逐个给组件中每个没有key的标签添加 key={Math.random()} ,直至错误消失,然后查看最后添加的那个标签。(可能耗时且有时无效)
  • 按时间顺序撤销更改,直到错误消失。(可能耗时)
  • 这里还有更好的方法

我正在寻找全面而权威的答案。


4
很可能,在你的组件中使用了map()函数将一个数组转换成JSX元素。在这样做的过程中,我怀疑你没有传递key属性给这些元素。你应该做一些像这样的事情:arr.map((element,key) => <div key={key}>{element}</div>) - Yevhen Horbunkov
通过上述方法,由map()生成的元素将具有唯一的key值(因为map()的第二个参数是数组中项目的索引)。理论上,Math.random()有一定的机会产生两次或更多次相同的输出,因此我认为这不是一个好的实践方式。 - Yevhen Horbunkov
@SherylHohman 不,它不会。请仔细阅读问题。 - mareoraft
1
如果你想使用随机密钥,最好的选择是使用nanoid - Christos Lytras
每次在JSX中将数组映射到列表时,您都可以使用此数组的索引作为键。如上所述以及下面所述,仅当列表是从数据数组动态生成时才会出现此错误。由于键应该是唯一的,因此Math库中的随机方法行不通。 - Martin Plávek
这个回答解决了你的问题吗?理解React.js中数组子元素的唯一键 - Liam
7个回答

18
你可以通过查找JSX中的map调用来找到有问题的部分。 map中的每个顶层元素都应该有一个key属性,即:
{items.map(item => (
  <div key={item.id}>
    <div>{item.name}</div>
    <div>{item.description}</div>
  </div>
))}

文档提供了一些关于此主题的解释,特别是:

 

键(Keys)有助于React识别已更改、已添加或已删除的项。应将键指定给数组内部的元素,以为元素提供稳定的身份

    

选择键的最佳方法是使用字符串,该字符串在其同级列表项中唯一标识列表项。通常,您会使用数据中的ID作为键

    

当您没有呈现项目的稳定ID时,可以使用项目索引作为键,作为最后的手段

    

如果项目顺序可能更改,则不建议使用索引用作键。这可能会对性能产生负面影响,并可能导致组件状态出现问题。

更新

如果您想使用Math.random,我认为更好的解决方案可能是使用UUIDv4。例如,此软件包可以生成它们。理论上可能会生成两个相似的UUID,但机会非常小,您需要在几秒钟内生成很多个(一些数字)。但是,我从未这样做过,也不能说使用UUID作为键会影响性能多少。根据文档关于键的说明,我猜想React将始终认为已删除了所有元素并添加了新元素。

因此,最好的解决方案是为每个项目关联一些ID。如果您呈现一个唯一字符串的数组,则项本身可以成为键。如果数组中的项目没有任何唯一标识符,并且项目的顺序永远不会更改并且无法从数组中删除项目,则使用index应该是一个安全选项。如果万不得已,您可以尝试使用uuid。

更新2

关于查找有问题的代码,我注意到此警告中存在如下跟踪:

index.js:1375 Warning: Each child in a list should have a unique "key" prop.

Check the render method of `Log`. See https://*b.me/react-warning-keys for more information.
    in div (at Log.js:241)
    in Log (created by ConnectFunction)
    in ConnectFunction (at HomePage.js:10)
    in WithWorkspace (created by ConnectFunction)
    in ConnectFunction (at HomePage.js:8)
    in HomePage (at App.js:24)
    in Route (at AuthenticatedRoute.js:14)
    in AuthenticatedRoute (created by ConnectFunction)
    in ConnectFunction (at App.js:23)
    in Switch (at App.js:22)
    in div (at App.js:21)
    in div (at App.js:18)
    in Unknown (created by ConnectFunction)
    in ConnectFunction (at FetchAll.js:165)
    in Unknown (created by ConnectFunction)
    in ConnectFunction (at FetchAll.js:164)
    in Unknown (created by ConnectFunction)
    in ConnectFunction (at FetchAll.js:163)
    in FetchAll (at App.js:17)
    in Router (created by BrowserRouter)
    in BrowserRouter (at App.js:15)
    in App (at src/index.js:14)
    in Provider (at src/index.js:13)

这里的有问题的文件名为Log.js,位于第241行。我不确定跟踪信息是否总是存在且正确,但它可能会有所帮助。

至于我,我经常在浏览器中检查结果,并通常打开控制台,因此当我看到警告时,通常知道我最近对数组做了什么以及我忘记键的位置。


10

根据我目前学到的知识,以下是部分答案。

导致/不导致此错误的因素如下:

  1. A tag in an array with a "key" prop missing causes the error. For example,

    <Fragment>
        {[        <div>one</div>    ]}
    </Fragment>
    

无论子元素数量如何,都会出现此错误。

  1. A tag not in an array with a "key" prop missing does not cause the error. For example,

    <Fragment>
        <div>one</div>
    </Fragment>```
    
无论子元素数量多少,都不会产生错误。
  1. A tag in an array with a "key" prop present with a value of undefined causes the error. For example,

    <Fragment>
        {[        <div key={undefined}>one</div>    ]}
    </Fragment>```
    

即使键 prop 被明确写出,也会出现错误。这一点非常重要,因为这意味着您可能会将变量赋值给键 prop,仍然会遇到此错误。例如,您可能会遇到错误数据进入应用程序,以至于 key={myobj.id} 会触发错误,因为 myobj.id 是未定义的。

  1. A tag in an array with a "key" prop present with duplicate defined values does not cause the error. For example,

    <Fragment>
        {[        <div key={'chicken'}>one</div>,        <div key={'chicken'}>one</div>    ]}
    </Fragment>```
    

即使键不是唯一的,也不会出现错误!

什么导致了这个错误? 总结如下:

当存在一个Array包含一个没有分配key属性或具有已分配值为undefinedkey属性的标记项时,该错误就会发生。


4

策略

有一个 ESLint 规则可以用来防止这种错误再次发生。

JSX-Key 缺失警告:

规则如何工作?

如果一个元素可能需要一个 key 属性,即在数组字面量或箭头函数表达式中出现的元素,则发出警告。

无效

[<Hello />, <Hello />, <Hello />];

有效的

[<Hello key="first" />, <Hello key="second" />, <Hello key="third" />];

Link:

https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/jsx-key.md


3

当您需要在React中呈现数组时,您将使用map函数。

如果您的渲染组件中有一个map函数,则其返回的根元素需要带有一个唯一的key属性。这是为了优化列表的渲染。

const names = ['John', 'Sam', 'Charlie'];
{
  names.map( (name, index) =>
    <div key={index}>
      <Foo />
      <Bar />
    </div>
  )
}

为了解决你的问题,您应首先确定在哪里映射了元素,然后添加属性。如果您的数组中没有唯一标识符,即使是索引(如上面的代码片段中所述)也是一个很好的选择。如果它是一个用户对象的数组;电子邮件ID或用户ID看起来很有前途。

2

这就是它!救命稻草! - Axe

1

@mareoraft@Gennady Dogaev给出了很好的答案并回答了大部分您的问题。

这是我的两分钱:

什么导致这个错误? 我正在寻找一个明确的列表。

数组中一个带有“key”属性的标记完全缺失(我很确定)

没错!您要么有重复的键,要么完全缺少键。

数组中具有相同“key”属性值的两个标记?(我认为这会有不同的错误消息)

具有相同键的两个元素将引发相同的警告。 警告不会在生产模式下显示,只会在dev模式下显示。 具有重复键也可能导致奇怪的行为:具有相同键的元素将无法正确更新或保留旧道具。 如果您列表中的元素不改变,则不会注意到这一点-例如:呈现列表中的元素永远不会更改。

两个并排编写的元素(如)算作“列表中的子元素”吗? 它们也会导致错误吗?

不会出错。"Keys 帮助 React 识别哪些项已更改、添加或删除。" - 更多详情 这些 div 是静态代码 - 它们永远不会更改,因此不需要 key。

如何有效地找到有问题的标签?

将key={Math.random()}添加到组件中的每个无key标签,逐个进行,直到错误消失,然后查看您最后添加的是哪一个。(可能很耗时,并且有时不起作用)

对于列表项使用随机键并不是一个好主意。 在每次渲染时生成随机键意味着即使属性没有改变,所有列表组件也会更新(重新渲染)。 我建议这只作为最后的选择,而对于大型列表(或应用程序)来说,这可能会导致性能问题。

当我没有id可用作键时,我喜欢使用index并组成一个键 - 例如:list.map((elem, index) => <div key={`some-element-${index}`}>{elem.name}</div>)。 虽然使用索引作为键被认为是反模式,但它确实可以帮助您解决此问题。

给键值设定名称后,您可以轻松找到引发警告的组件 - 例如,在代码中查找添加some-element-,因为警告显示了重复键的名称。

按时间顺序撤销更改,直到错误消失。(可能很耗时)

这可能有效,但您是正确的:这需要时间 - 不过,还有什么不需要时间呢? :)

在这里有更好的方法

您可以尝试使用eslint-plugin-react。他们有一个jsx-key规则,可能会对您有所帮助。虽然它可能仍然有一些限制,但总比没有要好。

希望这能帮到您!


感谢您提出 jsx-key 的建议。 - mareoraft

0

帮助我理解为什么在React中需要键的真正原因就是记住React是声明式编程。

您不再需要繁琐地处理addEventListenerremoveEventListener或隐式管理状态。您只需向React提供状态对象和JSX中的布局,它会在用户与您的应用程序交互时自动处理

为了使魔法起作用,React将您的UI转换为一堆对象,并在协调期间进行比较以将当前UI与应更改的方式进行比较。任何与预期UI不匹配的内容都会被替换掉。

数组是一个特殊的挑战,因为它们代表列表,在UI中经常会被排序、过滤等处理。当React在没有键的情况下执行协调时,所有列表项都会重新渲染。但是键为React提供了一种便宜的方法来比较对象;匹配不需要另一个渲染。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接