如何找到警告的原因:列表中的每个孩子都应该有一个唯一的“关键”道具

Posted

技术标签:

【中文标题】如何找到警告的原因:列表中的每个孩子都应该有一个唯一的“关键”道具【英文标题】:How to find the cause of the Warning: Each child in a list should have a unique "key" prop 【发布时间】:2020-05-11 04:43:06 【问题描述】:

我经常遇到错误

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

在反应。错误消息总是告诉你有问题的组件,但不告诉你有问题的特定 html 标记/虚拟 DOM 元素。在包含大型组件的大型代码库中工作,这使得查找错误来源变得非常困难。

是什么导致了这个错误?我正在寻找一个明确的清单。

数组中的标签完全缺失了“key”道具(非常确定) 数组中的两个标签具有相同的“key”prop 值? (我认为对此有不同的错误消息)

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

什么是找到攻击性标签的有效策略?

key=Math.random() 逐一添加到组件中的每个无钥匙标签,直到错误消失,然后查看您最后添加的标签。 (可能很耗时,有时不起作用) 按时间顺序撤消更改,直到错误消失。 (可能很耗时) 这里有更好的东西

我正在寻找一个全面而规范的答案。

【问题讨论】:

很可能,您在其中一个组件中使用map() 将数组转换为 JSX 元素。我怀疑这样做时,您没有将 key 属性传递给这些元素。你应该做点什么,比如:arr.map((element,key) => <div key=key>element</div>) 通过上述方法,由普通map() 生成的元素将具有唯一的key 值(因为map() 的第二个参数是指数组中项目的索引)。 Math.random(),理论上,有一定的机会产生两次或多次相同的输出,所以,我认为使用它不是一个好习惯。 @SherylHohman 不,它没有。请仔细阅读问题。 如果你想使用随机密钥,最好的选择是使用nanoid。 每次将数组映射到 JSX 中的列表时,您都可以使用此数组索引作为键。正如上面和下面提到的,只有当列表是从您的数据数组中动态生成时才会出现此错误。由于密钥应该是唯一的,因此数学库中的随机方法不会起作用。 【参考方案1】:

您可以通过在 jsx 中查找 map 调用来找到违规部分。 map 中的每个***元素都应具有key 属性,即

items.map(item => (
  <div key=item.id>
    <div>item.name</div>
    <div>item.description</div>
  </div>
))

Docs对此有一些解释,特别是:

键帮助 React 识别哪些项目已更改、添加或删除。应为数组内的元素赋予键,以使元素具有稳定的身份

选择键的最佳方法是使用一个字符串,该字符串在其兄弟项中唯一标识一个列表项。大多数情况下,您会使用数据中的 ID 作为键

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

如果项目的顺序可能发生变化,我们不建议对键使用索引。这会对性能产生负面影响,并可能导致组件状态出现问题。

UPD

如果你想使用Math.random,我认为更好的解决方案可能是使用 UUIDv4。例如,this package 可以生成它们。虽然理论上可以生成两个相似的 UUID,但机会非常低,您需要在几秒钟内生成很多 (some numbers)。但是,我从来没有这样做过,也不能说使用 UUID 作为关键影响性能有多大。鉴于文档中有关键的内容,我猜 react 将始终认为所有元素都已删除并添加了新元素。

因此,最好的解决方案是为每个项目关联一些 id。如果您呈现一个唯一字符串数组,则 item 本身可以是键。如果数组中的项目没有任何唯一的 id 并且项目的顺序永远不会改变并且项目不能从数组中删除,使用index 应该是一个安全的选择。作为最后的手段,您可以尝试 uuid。

UPD2

关于查找攻击性代码,我注意到这个警告中有一条痕迹,看起来像这样:

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 行。我不知道跟踪是否始终存在且正确,但它可能会有所帮助。

就我而言,我经常在浏览器中查看结果,并且控制台通常是打开的,所以当我看到该警告时,我通常知道我最近对数组做了什么以及我忘记了密钥的位置。

【讨论】:

【参考方案2】:

这是我目前所学的部分答案。

什么会/不会导致此错误?这是一个列表:

    数组中缺少“key”道具的标签导致错误。例如,

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

给出错误,不管孩子有多少。

    数组中的标签 not 缺少“key”道具会 not 导致错误。例如,

    <Fragment>
        <div>one</div>
    </Fragment>```
    

不给出错误,不管孩子有多少。

    数组中带有“key”属性的标签,其值为undefined导致错误。例如,

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

给出错误,即使键入了 key prop。意识到这一点很重要,因为这意味着您可以为 key prop 分配一个变量,而 仍然 会遇到此错误。例如,您可能有错误数据进入您的应用程序,因此 key=myobj.id 触发错误,因为 myobj.id 未定义。

    具有重复定义值的数组中的标签 not 会导致错误。例如,

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

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

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

当存在 Array 包含的项目是没有分配 key 属性的标签或具有分配值为 undefined 的键道具时,就会导致该错误。

【讨论】:

【参考方案3】:

当你必须在 React 中渲染一个数组时,你会使用 map 函数。

如果你的渲染组件中有一个 map 函数,它返回的根元素需要一个 key 属性,该属性必须是唯一的。这是为了优化列表的渲染。

const names = ['John', 'Sam', 'Charlie'];

  names.map( (name, index) =>
    <div key=index>
      <Foo />
      <Bar />
    </div>
  )

要解决MyComponent 中的问题,您应该首先确定映射元素的位置,然后添加key 属性。如果您的数组中没有任何唯一标识符,那么即使是索引(如上面的代码 sn-p 中提到的)也是一个很好的候选者。如果它是一组用户对象;电子邮件 ID 或用户 ID 看起来很有希望。

【讨论】:

【参考方案4】:

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

这是我的 2 美分:

是什么导致了这个错误?我正在寻找一个明确的清单。

数组中的标签完全缺少“key”道具(非常确定)

是的!您要么有重复的密钥,要么完全丢失了密钥

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

具有相同键的两个元素将引发相同的警告。 生产模式下不显示警告,仅在开发模式下显示。 拥有重复的键也会导致奇怪的行为:具有相同键的元素不会正确更新或保留旧的道具。如果列表中的元素没有改变,这并不明显 - 例如:渲染列表中的元素永远不会改变。

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

没有。这不会导致错误。 “键帮助 React 识别哪些项目已更改、添加或删除。” - more details 这些 div 是静态代码——它们永远不会改变,所以它们不需要密钥。


找到攻击性标签的有效策略是什么?

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

对列表项使用随机键并不是一个好主意。 在每次渲染时生成一个随机键意味着您列出的所有组件都会更新(重新渲染),即使道具没有改变。 我会将此作为最后的手段,对于大型列表(或应用程序),这可能会出现性能问题。

当我没有id 用作键而不是random 时,我喜欢使用index 并组合键 - 例如:list.map((elem, index) =&gt; &lt;div key=`some-element-$index`&gt;elem.name&lt;/div&gt;) 使用索引作为键被认为是anti-pattern,但它肯定可以帮助您通过这个问题。

拥有组合键名称后,您可以轻松找到引发警告的组件 - 例如:在代码中添加 some-element-,因为警告会显示重复键的名称。

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

这可能有效,但您是对的:这很耗时 - 再说一遍,什么不是? :)

这里有更好的东西

你可以试试eslint-plugin-react。他们有一个jsx-key 规则可能会对您有所帮助。尽管它可能仍然有一些限制,但它仍然比没有更多。

希望这会有所帮助!

【讨论】:

感谢jsx-key 的建议。【参考方案5】:

真正帮助我理解为什么 React 中的数组需要键是记住 React 是声明性编程。

您不必再玩弄addEventListenerremoveEventListener 或隐式管理状态。您只需在 JSX 中为 React 提供一个状态对象和一个布局,当用户对您的应用程序做出反应时,它计算出来

为了让魔法发挥作用,React 将你的 UI 变成一大堆对象,并运行 diff during reconciliation 来比较当前 UI 和它应该如何改变。任何与预期 UI 不匹配的内容都会被替换掉。

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

【讨论】:

【参考方案6】:

策略

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

JSX-Key 缺失警告:

规则如何运作?

如果某个元素可能需要键属性(即存在于数组字面量或箭头函数表达式中),则发出警告。

无效

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

有效

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

链接:

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

【讨论】:

以上是关于如何找到警告的原因:列表中的每个孩子都应该有一个唯一的“关键”道具的主要内容,如果未能解决你的问题,请参考以下文章

反应:警告列表中的每个孩子都应该有一个唯一的键[重复]

我该如何摆脱 - 警告:列表中的每个孩子都应该有一个唯一的“关键”道具[重复]

警告 - 列表中的每个孩子都应该有一个唯一的“关键”道具

无法摆脱“警告:列表中的每个孩子都应该有一个唯一的“关键”道具。” [复制]

React - 警告:列表中的每个孩子都应该有一个唯一的“关键”道具

警告:列表中的每个孩子都应该有一个唯一的“关键”道具。反应.js