Стабильные ключи и списки

Урок про key в списках: почему стабильные ключи критичны для корректности и производительности, и почему индекс — плохой ключ.

key — это «удостоверение личности» элемента списка. По ключу React понимает, какой элемент сохранить, какой добавить, а какой удалить между рендерами.

Зачем нужны ключи

Когда вы рендерите список через map, React при следующем рендере должен сопоставить старые элементы с новыми. Без подсказки он сравнивал бы по позиции — и при вставке в начало списка считал бы изменившимися все элементы. key даёт каждому элементу устойчивую идентичность: React переносит существующий DOM-узел и состояние к тому же ключу, а не пересоздаёт всё подряд.

{users.map((user) => (
  <UserCard key={user.id} user={user} />
))}

Почему индекс массива — плохой ключ

Соблазнительно написать key={index}. Это работает, пока список не меняет порядок и не вставляет/удаляет в середине. Иначе индексы «съезжают»: элемент с key=0 теперь указывает на другой объект данных. React решит, что это тот же элемент, и переиспользует его DOM и внутреннее состояние — что приводит к классическому багу: введённый в поле текст «прилипает» не к той строке после удаления.

КлючКогда безопасен
user.id (стабильный из данных)всегда — лучший выбор
индекс массиватолько если список статичен: не сортируется, не фильтруется, не меняет длину в середине
Math.random()никогда: новый ключ каждый рендер ⇒ полное пересоздание списка

Демонстрация «сдвига» в чистом JS

Покажем на запускаемом примере, как индекс перестаёт соответствовать элементу после вставки в начало. Ключи по id остаются привязаны к данным, а индексы — нет.

let list = [
  { id: "a", text: "Алиса" },
  { id: "b", text: "Борис" },
];

// что "видит" React, если ключ = индекс
console.log("До вставки (key=index):");
list.forEach((item, i) => console.log("  key", i, "=>", item.text));

// вставляем нового пользователя В НАЧАЛО
list = [{ id: "z", text: "Зоя" }, ...list];

console.log("После вставки (key=index):");
list.forEach((item, i) => console.log("  key", i, "=>", item.text));

console.log("Вывод: key=0 теперь Зоя, хотя раньше был Алиса — React переиспользует не тот узел");

Вывод:

До вставки (key=index):
  key 0 => Алиса
  key 1 => Борис
После вставки (key=index):
  key 0 => Зоя
  key 1 => Алиса
  key 2 => Борис
Вывод: key=0 теперь Зоя, хотя раньше был Алиса — React переиспользует не тот узел

Правила хороших ключей

  • Ключ должен быть стабильным (один и тот же элемент данных — всегда один ключ) и уникальным среди братьев.
  • Берите ключ из данных: id из БД, slug, естественный уникальный признак.
  • Нет id? Сгенерируйте стабильный идентификатор один раз при создании элемента (например, crypto.randomUUID() при добавлении), а не на каждый рендер.
  • Ключ нужен только на верхнем элементе, возвращаемом из map, и только среди соседей — глобальной уникальности не требуется.

Итог

  • key даёт элементу устойчивую идентичность между рендерами.
  • Индекс как ключ ломает порядок и состояние при вставках/сортировке/удалении.
  • Лучший ключ — стабильный id из данных; Math.random() — никогда.
Проверьте себя
1. Для чего React использует key в списках?
AДля стилизации элементов
BЧтобы дать элементу устойчивую идентичность между рендерами
CДля сортировки по алфавиту
DЭто необязательный атрибут без эффекта
2. Почему индекс массива — плохой ключ при вставке в начало списка?
AИндексы слишком длинные
BИндексы «съезжают», и React переиспользует DOM и состояние не того элемента
CReact запрещает числовые ключи
DЭто замедляет сортировку
3. Какой ключ предпочтителен?
AMath.random() для уникальности
BИндекс массива всегда
CСтабильный id из данных
DТекущее время Date.now()
Поддержать проект