Будьте осторожны при переборе массивов !!
Распространено заблуждение, что использование индекса элемента в массиве является приемлемым способом подавления ошибки, с которой вы, вероятно, знакомы:
Each child in an array should have a unique "key" prop.
Однако во многих случаях это не так! Это анти-паттерн, который в некоторых ситуациях может привести к нежелательному поведению .
Понимание key
опоры
React использует key
реквизит для понимания отношения компонента к элементу DOM, которое затем используется для процесса согласования . Поэтому очень важно, чтобы ключ всегда оставался уникальным , иначе есть хороший шанс, что React смешает элементы и изменит неправильный. Также важно, чтобы эти ключи оставались статичными течение всех повторных рендеров, чтобы поддерживать наилучшую производительность.
Это сказанное, не всегда нужно применять вышеупомянутое, если известно, что массив полностью статичен. Тем не менее, по возможности рекомендуется применять лучшие практики.
Разработчик React сказал в этом выпуске GitHub :
- ключ на самом деле не в производительности, а скорее в идентичности (что, в свою очередь, ведет к повышению производительности). случайно назначенные и изменяющиеся значения не идентичны
- Мы не можем реально предоставить ключи [автоматически], не зная, как моделируются ваши данные. Я хотел бы предложить, возможно, использовать какую-то функцию хеширования, если у вас нет идентификаторов
- У нас уже есть внутренние ключи, когда мы используем массивы, но они являются индексом в массиве. Когда вы вставляете новый элемент, эти ключи неверны.
Короче говоря, key
должно быть:
- Уникальный - ключ не может быть идентичным ключу родственного компонента .
- Статический - ключ не должен меняться между рендерами.
С использованием key
опору
Согласно приведенному выше объяснению, внимательно изучите следующие примеры и постарайтесь по возможности реализовать рекомендуемый подход.
Плохо (Потенциально)
<tbody>
{rows.map((row, i) => {
return <ObjectRow key={i} />;
})}
</tbody>
Возможно, это самая распространенная ошибка, возникающая при переборе массива в 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(
<div>
<button onClick={this.click}>Add</button>
<ul>
{this.state.arr.map(
(item, i) => <Item key={i} text={"Item " + i}>{item + " "}</Item>
)}
</ul>
</div>
);
}
}
const Item = (props) => {
return (
<li>
<label>{props.children}</label>
<input value={props.text} />
</li>
);
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
В этом фрагменте мы используем нестатический массив и не ограничиваемся его использованием в качестве стека. Это небезопасный подход (вы поймете почему). Обратите внимание, как при добавлении элементов в начало массива (в основном без смещения) значение каждого из них <input>
остается на месте. Почему? Потому что key
не уникально идентифицирует каждый элемент.
Другими словами, сначала Item 1
имеет key={0}
. Когда мы добавим второй элемент, верхний элемент становится Item 2
, а затем в Item 1
качестве второго пункта. Однако сейчас Item 1
есть key={1}
и не key={0}
больше. Вместо этого Item 2
теперь есть key={0}
!!
Таким образом, React считает, что <input>
элементы не изменились, потому что Item
ключ with 0
всегда находится вверху!
Так почему такой подход только иногда плох?
Такой подход опасен только в том случае, если массив каким-то образом фильтруется, переставляется или элементы добавляются / удаляются. Если он всегда статичен, то использовать его совершенно безопасно. Например, с ["Home", "Products", "Contact us"]
помощью этого метода можно безопасно перемещаться по меню навигации, например, так как вы, вероятно, никогда не добавите новые ссылки и не переставите их.
Короче говоря, вот когда вы можете безопасно использовать индекс как key
:
- Массив статичен и никогда не изменится.
- Массив никогда не фильтруется (отображать подмножество массива).
- Массив никогда не переупорядочивается.
- Массив используется как стек или LIFO (последний вошел, первый вышел). Другими словами, добавление может быть сделано только в конце массива (т. Е. Push), и только последний элемент может быть удален (т. Е. Pop).
Если бы мы вместо того, чтобы , в сниппет выше, толкнул добавленный элемент в конец массива, порядок для каждого существующего объекта всегда будет правильным.
Очень плохо
<tbody>
{rows.map((row) => {
return <ObjectRow key={Math.random()} />;
})}
</tbody>
Хотя этот подход, вероятно, гарантирует уникальность ключей, он всегда заставляет реагировать на повторную визуализацию каждого элемента в списке, даже если это не требуется. Это очень плохое решение, так как оно сильно влияет на производительность. Не говоря уже о том, что нельзя исключать возможность столкновения клавиш в том случае, Math.random()
если дважды получается одно и то же число.
Нестабильные ключи (наподобие тех, что созданы Math.random()
) приведут к ненужному воссозданию многих экземпляров компонентов и узлов DOM, что может привести к снижению производительности и потере состояния в дочерних компонентах.
Отлично
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uniqueId} />;
})}
</tbody>
Это, пожалуй, лучший подход, поскольку он использует свойство, уникальное для каждого элемента в наборе данных. Например, если rows
содержит данные, извлеченные из базы данных, можно использовать первичный ключ таблицы ( который обычно представляет собой число с автоинкрементом ).
Лучший способ выбрать ключ - использовать строку, которая однозначно идентифицирует элемент списка среди его братьев и сестер. Чаще всего вы используете идентификаторы из ваших данных в качестве ключей
Хорошо
componentWillMount() {
let rows = this.props.rows.map(item => {
return {uid: SomeLibrary.generateUniqueID(), value: item};
});
}
...
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uid} />;
})}
</tbody>
Это тоже хороший подход. Если ваш набор данных не содержит данных, которые гарантируют уникальность ( например, массив произвольных чисел ), существует вероятность столкновения ключа. В таких случаях лучше всего вручную генерировать уникальный идентификатор для каждого элемента в наборе данных, прежде чем выполнять его итерацию. Предпочтительно при монтировании компонента или при получении набора данных ( например, от props
или из асинхронного вызова API ), чтобы сделать это только один раз , а не каждый раз, когда компонент повторно выполняет рендеринг. Уже есть несколько библиотек, которые могут предоставить вам такие ключи. Вот один пример: реакция-ключ-индекс .
key
свойство. Это поможет ReactJS найти ссылки на соответствующие узлы DOM и обновить только содержимое внутри разметки, но не перерисовывать всю таблицу / строку.