理解在React.js中用于数组子元素的唯一键值
理解在React.js中用于数组子元素的唯一键值
我正在构建一个 React 组件,可以接受一个 JSON 数据源并创建一个可排序的表格。
每个动态数据行都有一个唯一的键,但我仍然收到以下错误:
数组中的每个子级都应该有一个唯一的“key”属性。
请检查 TableComponent 的呈现方法。
我的 TableComponent
呈现方法返回:
{ rows }
TableHeader
组件是一个单行,也有一个唯一的键。
rows
中的每个 row
都由具有唯一键的组件构建:
TableRowItem
如下所示:
var TableRowItem = React.createClass({ render: function() { var td = function() { return this.props.columns.map(function(c) { return{this.props.data[c]}
; }, this); }.bind(this); return (
{ td(this.props.item) }
) } });
是什么导致了唯一键属性的错误?
在迭代数组时要小心!
有一个常见的误解,即使用数组中元素的索引是一种可接受的方式,可以抑制您可能熟悉的错误:
Each child in an array should have a unique "key" prop.
但是,在许多情况下,这并不是这样的!这是一种反模式,可能在某些情况下导致不良行为。
理解key
属性
React使用key
prop来理解组件与DOM元素的关系,然后用于协调过程。 因此,关键始终保持唯一非常重要,否则React有很大几率混淆元素并修改不正确的元素。同样重要的是,这些键在所有重新渲染过程中保持静态,以保持最佳性能。
话虽如此,只要知道数组完全是静态的,就不必总是应用上述方法。但是,尽可能应用最佳实践是受到鼓励的。
一个React开发人员在此GitHub问题中说:
- 关键是不是真的关于性能,而是更多关于身份(从而导致更好的性能)。随机分配和更改值不是身份
- 如果不知道如何对数据进行建模,我们实际上无法自动提供键[自动]。如果没有ID,我建议使用某种哈希函数
- 当我们使用数组时,我们已经有了内部密钥,但它们是数组中的索引。插入新元素时,这些密钥就不对了。
简而言之,key
应该是:
- 唯一 - 一个键不能与兄弟组件的键相同。
- 固定 - 一个键应该在重新渲染时保持不变。
使用 key
属性
根据上面的解释,仔细研究以下示例,并在可能的情况下尝试实现建议的方法。
不好(潜在的)
{rows.map((row, i) => { return; })}
当在React中迭代数组时,这可以说是最常见的错误。这个方法在技术上并没有“错误”,只是如果你不知道你在做什么,那就是“危险”的。如果你正在遍历一个静态数组,那么这是一个完全有效的方法(例如你的导航菜单中的链接数组)。然而,如果你正在添加,删除,重新排序或过滤项目,那么你就需要小心了。在官方文档中查看这个详细的解释。
class MyApp extends React.Component { constructor() { super(); this.state = { arr: ["Item 1"] } } click = () => { this.setState({ arr: ['Item ' + (this.state.arr.length+1)].concat(this.state.arr), }); } render() { return(
-
{this.state.arr.map(
(item, i) =>
- {item + " "} )}
在这个代码片段中,我们正在使用一个非静态数组,并且我们不限制自己将其用作堆栈。这是一种不安全的方法(你会看到为什么)。请注意,当我们添加项目到数组的开头(基本上是unshift)时,每个的值保持不变。为什么?因为
key
没有唯一标识每个项。
换句话说,一开始Item 1
的key={0}
。当我们添加第二个项目时,顶部项目变成Item 2
,其后是Item 1
作为第二个项目。然而,现在Item 1
的key={1}
而不是key={0}
了。取而代之的是,Item 2
现在有了key={0}
!
因此,React 认为 元素没有变化,因为具有键为
0
的 Item
一直在顶部!
那么为什么这种方法只有有时是不好的?
如果数组被过滤、重新排列或添加/删除项,则此方法仅在这种情况下才具有风险。如果它始终是静态的,那么使用它是完全安全的。例如,像 ["Home", "Products", "Contact us"]
这样的导航菜单可以使用此方法安全地迭代,因为您可能永远不会添加新链接或重新排列它们。
简而言之,以下是可以 安全地 使用索引作为 key
的情况:
- 数组是静态的,并且永远不会改变。
- 数组从未被过滤(显示数组的子集)。
- 数组从未被重新排序。
- 数组用作堆栈或 LIFO(后进先出)。换句话说,只能在数组末尾添加(即推入),并且只能删除最后一项(即弹出)。
如果我们在上面的片段中将添加的项目 推送 到数组末尾,每个现有项目的顺序将始终正确。
非常糟糕
{rows.map((row) => { return; })}
虽然这种方法可能保证键的唯一性,但它始终会强制 React 重新呈现列表中的每个项目,即使不需要。这是一个非常糟糕的解决方案,因为它会极大地影响性能。更不用说不能排除在 Math.random()
产生两次相同数字的情况下出现键冲突的可能性。
不稳定的键(比如由
Math.random()
产生的键)将导致许多组件实例和DOM节点不必要地被重新创建,这可能会导致性能下降并且子组件状态丢失。
非常好
{rows.map((row) => { return; })}
这可能是最好的方法,因为它使用了一个在数据集中每个项目中都是唯一的属性。例如,如果rows
包含从数据库获取的数据,则可以使用表的主键(通常是一个自增的数字)。
选择键的最佳方法是使用一个字符串,该字符串在其兄弟姐妹中唯一标识列表项。通常,您会使用数据中的ID作为键。
好
componentWillMount() { let rows = this.props.rows.map(item => { return {uid: SomeLibrary.generateUniqueID(), value: item}; }); } ... {rows.map((row) => { return; })}
这也是一个好方法。如果您的数据集不包含任何保证唯一性的数据(例如一组任意数字的数组),则存在键冲突的可能性。在这种情况下,最好在迭代数据集之前为每个项手动生成一个唯一标识符。最好在组件挂载或接收数据集时(例如从props
或异步API调用中),这样只需执行一次,而不是每次组件重新渲染时都去生成。已经有一些库可以提供这样的键。以下是一个示例:react-key-index。
你应该给每个子元素 以及子元素内的每个元素 添加一个键。
这样React就可以处理最小的DOM变化。
在你的代码中,每个
都试图渲染一些没有键的子元素。
请查看 这个例子。
尝试从 div 中的 元素中删除
key={i}
并查看控制台。
在示例中,如果我们没有给 元素提供键,并且我们想要更新 仅 它的
object.city
,React需要重新渲染整个行而不仅仅是这个元素。
这是代码:
const data = [ { name: "Nuri", age: 28, city: "HO" }, { name: "Talib", age: 82, city: "HN" }, { name: "Jenny", age: 41, city: "IT" }, ]; const ExampleComponent = React.createClass({ render: function () { const infoData = this.props.info; return ( {infoData.map((object, i) => { return ( {[ object.name, // remove the key {" "} {object.city}{" "} , object.age, ]} ); })} ); }, }); React.render(, document.body);
底部由@Chris发布的答案比这个答案更详细。
关于调和过程中键的重要性的React文档: 键