[英]Why React fails to render changes in array of JSX element when using .map index as a key?
在此代码库中,有React应用程序呈现ToDo的列表。 它使用.map()
index
参数作为key
来呈现此列表。 是的-我知道这是这种行为的根源,所以请不要在回答中告诉这个问题。
初始加载后,我在此添加了一些项目,然后看到了:
然后我按日期对它进行排序,然后在第一项中输入内容
然后单击“ Sort by Latest
并得到以下信息:
问题:在此特定示例中,当使用.map索引作为键时,为什么React无法在JSX元素数组中呈现更改?
PS这是为方便起见的代码:
const ToDo = props => (
<tr>
<td>
<label>{props.id}</label>
</td>
<td>
<input />
</td>
<td>
<label>{props.createdAt.toTimeString()}</label>
</td>
</tr>
);
class ToDoList extends React.Component {
constructor() {
super();
const date = new Date();
const todoCounter = 1;
this.state = {
todoCounter: todoCounter,
list: [
{
id: todoCounter,
createdAt: date,
},
],
};
}
sortByEarliest() {
const sortedList = this.state.list.sort((a, b) => {
return a.createdAt - b.createdAt;
});
this.setState({
list: [...sortedList],
});
}
sortByLatest() {
const sortedList = this.state.list.sort((a, b) => {
return b.createdAt - a.createdAt;
});
this.setState({
list: [...sortedList],
});
}
addToEnd() {
const date = new Date();
const nextId = this.state.todoCounter + 1;
const newList = [
...this.state.list,
{id: nextId, createdAt: date},
];
this.setState({
list: newList,
todoCounter: nextId,
});
}
addToStart() {
const date = new Date();
const nextId = this.state.todoCounter + 1;
const newList = [
{id: nextId, createdAt: date},
...this.state.list,
];
this.setState({
list: newList,
todoCounter: nextId,
});
}
render() {
return (
<div>
<code>key=index</code>
<br />
<button onClick={this.addToStart.bind(this)}>
Add New to Start
</button>
<button onClick={this.addToEnd.bind(this)}>
Add New to End
</button>
<button onClick={this.sortByEarliest.bind(this)}>
Sort by Earliest
</button>
<button onClick={this.sortByLatest.bind(this)}>
Sort by Latest
</button>
<table>
<tr>
<th>ID</th>
<th />
<th>created at</th>
</tr>
{this.state.list.map((todo, index) => (
<ToDo key={index} {...todo} />
))}
</table>
</div>
);
}
}
ReactDOM.render(
<ToDoList />,
document.getElementById('root')
);
似乎这里的输入根本没有更改,并且由于不受控制(因此它们的属性/文本值没有更改),因此在React对帐的上下文中根元素没有更改(它仍然具有相同的key
)。 然后,React转到最终不同的文本节点,然后仅刷新(更新DOM)那些已更改的节点。
因此,有两种变体可以使这项工作:
key
,而不是.map()
index
<input />
通常,第一种方法更好,因为key
属性会使根元素(在我们的示例中为整个ToDo
)无效,并强制其呈现自身,从而节省了对嵌套子树的比较。
问题:在此特定示例中,当使用.map索引作为键时,为什么React无法在JSX元素数组中呈现更改?
不要将列表索引用作键。 关键是要识别您从一个渲染到另一个渲染的各个条目。 当您更改数组的顺序时,react不会知道任何更改,因为todo数组中索引的顺序仍然是0, 1, 2, 3 ...
而是使用todo.id作为键。
React并未呈现新订单。 完全符合预期。 您告诉它的顺序与上次渲染完全相同,但是各个待办事项的属性已更改。
您要说的是顺序已更改。 然后,您必须传递一个有意义的密钥。 然后react将使用相同的dom元素,但是会更改顺序。
这意味着您可以执行诸如react-shuffle之类的操作 ,在该过渡效果中,您可以查看元素的重新排序方式。
这是因为您没有稳定的ID(您的索引在过滤后会更改)。 在此处查看文档。
还有另一个问题,因为对列表进行排序时会对其进行突变:
sortByEarliest() {
const sortedList = this.state.list.sort((a, b) => {
return a.createdAt - b.createdAt;
});
this.setState({
list: [...sortedList],
});
}
sortByLatest() {
const sortedList = this.state.list.sort((a, b) => {
return b.createdAt - a.createdAt;
});
this.setState({
list: [...sortedList],
});
}
排序会改变您的数组。 因此,您可以使用以下解决方案:
sortByEarliest() {
const { list } = this.state;
list.sort((a, b) => a.createdAt - b.createdAt);
this.setState({ list });
}
sortByLatest() {
const { list } = this.state;
list.sort((a, b) => b.createdAt - a.createdAt);
this.setState({ list });
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.