В ReactJS использование индекса массива в качестве ключа считается неэффективным, но я не могу заставить его работать неправильно?
Я не могу заставить его работать неправильно, если я использую индекс в качестве ключа. Единственный способ - это изменить массив и использовать индекс в качестве ключа. Но поскольку в документах говорится, что не следует изменять состояние (массив), поэтому, если это так, я не могу заставить его работать неправильно, вопреки тому, что говорится в документах. Как я могу показать, что он может сломаться?
function App() {
const [arr, setArr] = React.useState(["A","B","C"]);
function toggleSortOrder() {
let newArr = [...arr];
newArr.reverse();
console.log(JSON.stringify(newArr));
setArr(newArr);
}
return (
<div>
<ul>
{ arr.map((e, i) => <li key={i}>{ e }</li>) }
</ul>
<button onClick={toggleSortOrder} >Toggle Sort Order</button>
</div>
)
}
ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react@16.12.0/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Я могу заставить его сломаться, если изменю состояние, чего в документации делать не следует:
function App() {
const [arr, setArr] = React.useState(["A","B","C"]);
function toggleSortOrder() {
arr.reverse();
console.log(JSON.stringify(arr));
setArr(arr);
}
return (
<div>
<ul>
{ arr.map((e, i) => <li key={i}>{ e }</li>) }
</ul>
<button onClick={toggleSortOrder} >Toggle Sort Order</button>
</div>
)
}
ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react@16.12.0/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Но я даже не могу его сломать, если изменю состояние и использую индекс в качестве ключа, если это компонент класса:
class App extends React.Component {
state = { arr: ["A","B","C"] };
toggleSortOrder() {
this.state.arr.reverse();
console.log(JSON.stringify(this.state.arr));
this.setState({ arr: this.state.arr });
}
render() {
return (
<div>
<ul>
{ this.state.arr.map((e, i) => <li key={i}>{ e }</li>) }
</ul>
<button onClick={this.toggleSortOrder.bind(this)} >Toggle Sort Order</button>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react@16.12.0/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
2 ответа
Ключи в первую очередь о производительности. Tell реагирует, какой старый компонент соответствует какому новому, что, в свою очередь, помогает ему быстро определить минимальный набор изменений, необходимых для обновления dom. См. Документацию по сверки.
Таким образом, ваш код "ломается" в том смысле, что react думает, что ему нужно отредактировать текст внутри каждого из <li>
, когда можно было их переставить. Вы действительно не заметите никаких проблем с производительностью с вашим примером игрушки, но вы можете создать другие примеры, где это имеет гораздо большее значение. Вот тот, где я визуализирую 20000 строк и перемещаю одну строку в конец. Производительность совсем не велика, но лучше, если ключи используются правильно, а не на основе индекса.
const exampleData = [];
for (let i = 0; i < 20000; i++) {
exampleData[i] = {
key: i,
name: "Element " + i,
}
}
function App() {
const [useKeysWell, setUseKeysWell] = React.useState(false);
const [data, setData] = React.useState(exampleData);
function move() {
setData(prev => {
let newData = [...prev.slice(1), prev[0]];
return newData;
});
}
const before = Date.now();
setTimeout(() => {
console.log('reconciling and committing', Date.now() - before, 'ms (approx)');
}, 0)
return (
<div>
<button onClick={move}>Move one</button>
<button onClick={() => setUseKeysWell(prev => !prev)}>Use keys {useKeysWell ? "worse" : "better"} </button>
<ul key={useKeysWell}>
{ data.map((e, i) => <li key={useKeysWell ? e.key : i}>{ e.name }</li>) }
</ul>
</div>
)
}
ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react@16.12.0/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
С более сложным кодом вы также можете вызвать проблемы, если у вас есть компоненты с хуками жизненного цикла, которые вы ожидаете запустить, но из-за неправильных ключей компоненты не монтируются / размонтируются / обновляются не так, как вы ожидаете.
Реальность такова, что с помощью индекса map
функция будет работать, это не рекомендуется, потому что это анти-шаблон:
Если массив изменяется путем добавления или удаления элемента или изменения количества элементов в массиве, React будет считать, что элемент DOM представляет тот же компонент, что и раньше. Это означает, что влияние использования индекса может быть непредсказуемым, например, два элемента массива содержат один и тот же идентификатор.
Поэтому рекомендуется:
Если элементы вашего массива содержат уникальный идентификатор или вы можете сформировать уникальный идентификатор на основе структуры, сделайте это.
Используйте глобальный индекс, такой как счетчик, для создания динамических идентификаторов... например:
arr.map(elem => {
counter++;
return (
<li key={elem + counter}>{ elem }</li>);
);
});
Таким образом вы обеспечите key
имеет уникальный идентификатор.