如何找到警告的原因:列表中的每个孩子都应该有一个唯一的“关键”道具
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) => <div key=`some-element-$index`>elem.name</div>)
使用索引作为键被认为是anti-pattern,但它肯定可以帮助您通过这个问题。
拥有组合键名称后,您可以轻松找到引发警告的组件 - 例如:在代码中添加 some-element-
,因为警告会显示重复键的名称。
撤消按时间顺序更改,直到错误消失。 (可能很耗时)
这可能有效,但您是对的:这很耗时 - 再说一遍,什么不是? :)
这里有更好的东西
你可以试试eslint-plugin-react。他们有一个jsx-key 规则可能会对您有所帮助。尽管它可能仍然有一些限制,但它仍然比没有更多。
希望这会有所帮助!
【讨论】:
感谢jsx-key
的建议。【参考方案5】:
真正帮助我理解为什么 React 中的数组需要键是记住 React 是声明性编程。
您不必再玩弄addEventListener
、removeEventListener
或隐式管理状态。您只需在 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
【讨论】:
以上是关于如何找到警告的原因:列表中的每个孩子都应该有一个唯一的“关键”道具的主要内容,如果未能解决你的问题,请参考以下文章
我该如何摆脱 - 警告:列表中的每个孩子都应该有一个唯一的“关键”道具[重复]
无法摆脱“警告:列表中的每个孩子都应该有一个唯一的“关键”道具。” [复制]