Разработчики React постоянно работают с динамическими списками. Рендер массива элементов — рутина, но именно здесь кроется коварная ошибка, которая может привести к катастрофическим последствиям: багам с состоянием, неожиданному поведению компонентов и падению производительности. Всё начинается с безобидной строки: key={index}
.
Механика ключей: что React делает под капотом
При ререндере списка React использует ключи для сопоставления элементов до и после обновления. Без ключа React сравнивает деревья путём рекурсивного обхода — дорогостоящей операции. Ключи позволяют React:
- Определять идентичность элемента даже при изменениях порядка
- Избегать ненужного ререндера стабильных элементов
- Сохранять DOM-состояние полей ввода, фокуса и внутреннего состояния компонента
Когда вы используете index
, возникает фатальный недостаток: ключ перестаёт соответствовать уникальности и неизменности данных. Рассмотрим классический антипаттерн:
// Данные с уникальными ID во внутренней структуре
const users = [
{ id: 'u1', name: 'Alice' },
{ id: 'u2', name: 'Bob' },
];
const UserList = () => {
return (
<ul>
{users.map((user, index) => (
<UserItem
key={index} // ⚠️ Дьявол кроется здесь
name={user.name}
/>
))}
</ul>
);
};
Взрывной сценарий: что пойдёт не так
Сценарий 1: Удаление элемента посередине списка
Удалим «Alice» из массива. React увидит:
До:
0: Alice (key=0)
1: Bob (key=1)
После:
0: Bob (key=0)
React сопоставит ключи и подумает: «Элемент 0 (Alice) изменился на Bob, а элемент 1 (Bob) пропал». Вместо аккуратного удаления Alice компонент Bob получит неожиданные пропсы, обновит состояние ниже по дереву, а React может назначить неправильные DOM-атрибуты, включая фокус на инпутах.
Сценарий 2: Сортировка
Добавляем фильтр сортировки по имени:
До сортировки:
0: Alice (key=0)
1: Bob (key=1)
После сортировки (Z-A):
0: Bob (key=0) // Реакт "думает", что элемент key=0 изменился с Alice на Bob
1: Alice (key=1) // ...а key=1 изменился с Bob на Alice
Компоненты будут ререндериться полностью вместо простой перестановки в DOM. Портится производительность и ломается внутреннее состояние: представьте чекбокс, внезапно перескакивающий на другую строку.
Правильные стратегии генерации ключей
Используйте уникальный бизнес-идентификатор
Если данные приходят с бэкенда, используйте те ID, которые заложены в структуре данных:
{products.map(product => (
<ProductCard key={product.id} {...product} />
))}
Генерация клиентских ключей (если своих ID нет)
Когда данные временные или локальные, создайте хэш на основе уникальных свойств:
const TodoList = ({ todos }) => {
const generateKey = (todo) => `${todo.timestamp}-${todo.text.substring(0, 5)}`;
return (
<>
{todos.map(todo => (
<TodoItem key={generateKey(todo)} todo={todo} />
))}
</>
);
};
Криптографически устойчивые ключи
Для чувствительных данных (хотя это редко в UI) применяйте crypto.randomUUID
:
const SessionItems = () => {
const sessions = useSessions(); // Массив без ID
return (
<>
{sessions.map(session => (
<div key={crypto.randomUUID()}>{session.data}</div>
))}
</>
);
};
Когда индекс можно использовать?
Исключения подтверждают правило: – Статические списки, где порядок/количество гарантированно не меняются (например, список языков в настройках) – Данные "только для чтения" без компонентов с внутренним состоянием – Прототипирование, если вы контролируете весь поток данных
Но помните: такую компоненту быстро можно регистрировать больно: добавление состояния или фильтрации запустит баги.
Вы ошиблись? Как отловить проблему
React 18+ выводит строгие предупреждения в консоль:
Warning: Each child in a list should have a unique "key" prop.
Но он не различает «уникальный» и «уникальный и стабильный». Добавьте линтер-правило:
.eslintrc.js:
rules: {
'react/no-array-index-key': 'error',
}
Альтернатива для advanced: library-key
В библиотеках с виртуализацией (т.е. react-virtualized) используют ключи, привязанные к позиции данных (например, key={startIndex}
). Это искусственный сценарий, где индекс работает, потому что видимый диапазон данных стабилен ниже конкретной границы прокрутки.
Заключение: педантичность окупается
Фраза "используйте уникальные стабильные ключи" звучит как риторика. Но за ней стоит инженерная реальность: неправильные ключи порождают непредсказуемые баги, которые на отладку требуют часов. Ирония в том, что решение простое: либо 10 секунд найти ID, либо добавить хэш. В критичных для производительности списках разница заметна и в профилировщике.
Старожилы React помнят эпоху до введения key
, когда ререндеры списков из 100+ элементов блокировали UI на секунды. Современная рекомендация — не регрессия к тем временам, а цена ошибки сейчас дороже из-за сложности SPA.
Проверьте прямо сейчас: в любом списке вашего проекта везде, где есть key={index}
, замените его на реальный идентификатор. Если проект вдруг стал стабильнее — вы не один.