每个React的初学者,在调试程序时,都会遇到这样的警告:Warning: Each child in a list should have a unique "key" prop. 如下面的代码:
const list = ['Learn React', 'Learn GraphQL'];
const ListWithoutKey = () => (
<div>
<ul>
{list.map((item) => (
<li>{item}</li>
))}
</ul>
</div>
);
这个警告提示我们,需要为列表中的每个元素添加 key 属性。如下面的代码:
const ListWithoutKey = () => (
<div>
<ul>
{list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
使用上面的代码调试时,之前的警告会消失,但其实在其背后,有一个隐式的 bug。当你对列表进行重新排序时,特别是当你添加了非受控的元素,问题就会发生。
如果你的代码如下:
const initialList = ['Learn React', 'Learn GraphQL'];
const ListWithUnstableIndex = () => {
const [list, setList] = React.useState(initialList);
const handleClick = event => {
setList(list.slice().reverse());
};
return (
<div>
<ul>
{list.map((item, index) => (
<li key={index}>
<label>
{item}
</label>
</li>
))}
</ul>
<button type="button" onClick={handleClick}>
Reverse List
</button>
</div>
);
};
运行你的代码,点击按钮对列表进行重新排序,看样子,一切正常。
可是当你添加了不受控的元素时,假如你的代码如下:
const initialList = ['Learn React', 'Learn GraphQL'];
const ListWithUnstableIndex = () => {
const [list, setList] = React.useState(initialList);
const handleClick = event => {
setList(list.slice().reverse());
};
return (
<div>
<ul>
{list.map((item, index) => (
<li key={index}>
<label>
<input type="checkbox" />
{item}
</label>
</li>
))}
</ul>
<button type="button" onClick={handleClick}>
Reverse List
</button>
</div>
);
};
在上述的代码中,checkbox 是非受控元素,当你运行上述的代码时,运行的结果,可能和你相像的不太一致。
// 列表最初的样子
[x] Learn React
[ ] Learn GraphQL
// 点击按钮,列表重新排序后的样子
[x] Learn GraphQL
[ ] Learn React
这种结果显然不是你想要的,那这背后终究发生了什么呢?
// 列表当初的样子
[x] Learn React (index = 1)
[ ] Learn GraphQL (index = 2)
// 点击按钮,列表重新排序后的样子
[x] Learn GraphQL (index = 1)
[ ] Learn React (index = 2)
那如何解决这个问题呢,办法当然有,这次,我们使用了相当稳定的元素作为 key 属性。代码如下:
const initialList = [
{ id: 'a', name: 'Learn React' },
{ id: 'b', name: 'Learn GraphQL' },
];
const ListWithStableIndex = () => {
const [list, setList] = React.useState(initialList);
const handleClick = event => {
setList(list.slice().reverse());
};
return (
<div>
<ul>
{list.map(item => (
<li key={item.id}>
<label>
<input type="checkbox" />
{item.name}
</label>
</li>
))}
</ul>
<button type="button" onClick={handleClick}>
Reverse List
</button>
</div>
);
};
点击按钮,列表重新排序后,背后发生了变化。
// 列表最初的样子
[x] Learn React (id = a)
[ ] Learn GraphQL (id = b)
// 点击按钮,列表重新排序后的样子
[ ] Learn GraphQL (id = b)
[x] Learn React (id = a)
在这里,我们使用了 id 作为 key 属性,当然,你也可以使用列表中的其他元素,但前提是这个元素是不可改变的唯一值。
不管怎样,仍然值得注意的是,只要你的列表保持的顺序或大小没有改变,使用索引是可以的。然后,列表中每个项目的位置不会改变——它与索引一样稳定——因此使用索引是可以的。