Списки и ключи (keys)
При рендеринге массива компонентов React требует уникальный атрибут key для каждого элемента — он помогает алгоритму reconciliation эффективно определять какие элементы изменились, добавились или удалились.
Зачем нужно
Без key React не может различить элементы списка: при добавлении нового элемента в начало он перерисовывает весь список заново. С key React находит изменения точечно: перемещает, обновляет или удаляет только нужные элементы. Неправильный key (индекс массива) при сортировке или фильтрации вызывает баги: state компонентов «перескакивает» между элементами.
Где используется
- Рендеринг любых списков через
.map: товары, пользователи, комментарии - Динамические списки с добавлением/удалением/сортировкой
- Динамические табы, секции, компоненты
Правила для key
// ✓ Хорошо — стабильный уникальный идентификатор из данных
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
// ✓ Хорошо — уникальная строка (slug, uuid)
{posts.map(post => (
<PostCard key={post.slug} post={post} />
))}
// ✗ Плохо — индекс массива при изменяемом списке
{items.map((item, index) => (
<Item key={index} item={item} /> // баги при сортировке/добавлении
))}
// △ Допустимо — индекс для статических неизменяемых списков
{['Январь', 'Февраль', 'Март'].map((month, i) => (
<li key={i}>{month}</li> // список не изменится — индекс OK
))}
Почему индекс опасен
// Список с инпутами
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Купить молоко' },
{ id: 2, text: 'Позвонить другу' },
]);
const addAtBeginning = () => {
setTodos(prev => [{ id: Date.now(), text: 'Новая задача' }, ...prev]);
};
return (
<>
<button onClick={addAtBeginning}>Добавить в начало</button>
<ul>
{todos.map((todo, index) => (
// key={index}: при добавлении в начало
// React думает что первый элемент НЕ изменился (key=0 тот же)
// Но теперь key=0 → 'Новая задача', а input помнит 'Купить молоко'
// → state инпута «перескакивает» на новый элемент (БАГ!)
<li key={index}>
<span>{todo.text()}</span>
<input placeholder="Заметка к задаче" />
</li>
))}
</ul>
</>
);
}
// Правильно: key={todo.id} — React отслеживает по id, не по позиции
key как механизм сброса state
// Хак: изменение key полностью пересоздаёт компонент
// Полезно для сброса state при смене данных
function UserEditor({ userId }) {
return (
// При смене userId компонент пересоздаётся с нуля
// Не нужно вручную сбрасывать state формы
<EditForm key={userId} userId={userId} />
);
}
Компонент-обёртка при рендеринге массива
// Если не нужен реальный DOM-элемент для группировки
import { Fragment } from 'react';
function DefinitionList({ terms }) {
return (
<dl>
{terms.map(term => (
// Fragment с key — не добавляет лишний DOM-элемент
<Fragment key={term.id}>
<dt>{term.word}</dt>
<dd>{term.definition}</dd>
</Fragment>
))}
</dl>
);
}
Частые ошибки
key={index}при сортируемых/фильтруемых списках — state компонентов привязывается к позиции, а не к данным; используйтеidиз данных.- Генерируют key динамически —
key={Math.random}илиkey={Date.now()}в рендере создают новый ключ при каждом рендере → все элементы пересоздаются. - key на неверном уровне —
keyдолжен быть на корневом элементе внутри.map, а не на дочернем. - key не уникален в рамках списка — дублирующиеся key → непредсказуемое поведение.