[英]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.