Списки и ключи (key)

Превращаем массивы данных в списки элементов и разбираемся, зачем React требует уникальный key.

key — специальный проп, по которому React отличает элементы списка между перерисовками, чтобы обновлять DOM точечно.

Рендер массива через map

Чтобы показать список, данные превращают в массив JSX-элементов методом map. React умеет рендерить массив элементов:

function TodoList() {
  const todos = ["Купить хлеб", "Позвонить маме", "Сделать React"];
  return (
    <ul>
      {todos.map((todo) => (
        <li>{todo}</li>
      ))}
    </ul>
  );
}

Код пока неполон — React выдаст предупреждение «Each child in a list should have a unique key prop».

Зачем нужен key

Когда список меняется (добавили, удалили, переставили элемент), React сравнивает старую и новую версии. Без подсказки ему трудно понять, какой элемент какому соответствует. key — это «удостоверение личности» элемента: по нему React точно знает, что обновить, что оставить, а что удалить.

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

Ключ должен быть уникальным среди соседей и стабильным — обычно это id из данных.

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

Соблазн использовать индекс (map((item, i) => ... key={i})) велик, но это источник коварных багов. Индекс привязан к позиции, а не к элементу. Если вставить элемент в начало списка, все индексы сдвинутся, и React решит, что поменялись все элементы.

Покажем на JS, почему индекс «съезжает» при вставке в начало:

let items = [{ id: 7, text: "B" }, { id: 9, text: "C" }];
items.forEach((it, i) => console.log("индекс", i, "→ id", it.id));
console.log("--- вставили новый элемент в начало ---");
items = [{ id: 5, text: "A" }, ...items];
items.forEach((it, i) => console.log("индекс", i, "→ id", it.id));

Вывод:

индекс 0 → id 7
индекс 1 → id 9
--- вставили новый элемент в начало ---
индекс 0 → id 5
индекс 1 → id 7
индекс 2 → id 9

Видно: элемент с id 7 был под индексом 0, а стал под индексом 1. Если ключом был индекс, React «перепутает» элементы — например, текст в поле ввода окажется не у того элемента. С ключом по id такого не случится: id привязан к самому элементу.

Когда индекс допустим

Только если список статичен: не меняется порядок, не добавляются и не удаляются элементы. В остальных случаях — стабильный id.

Где ставить key

Ключ ставится на самый внешний элемент внутри map, а не где-то внутри:

{users.map((u) => (
  <div key={u.id} className="card">
    <h3>{u.name}</h3>
  </div>
))}

Итог

  • Списки рендерят через map, превращая данные в массив JSX-элементов.
  • Каждому элементу нужен key — уникальный и стабильный (обычно id).
  • Индекс как ключ ломает списки при вставке/удалении/перестановке; key ставят на внешний элемент.
Проверьте себя
1. Каким методом обычно рендерят список данных в JSX?
AforEach
Bmap
Cfilter
Dreduce
2. Зачем React нужен проп key у элементов списка?
AДля стилизации элементов
BЧтобы отличать элементы между перерисовками и обновлять DOM точечно
CЧтобы задать порядок сортировки
DЭто необязательный декоративный атрибут
3. Почему индекс массива — плохой выбор для key в изменяемом списке?
AИндекс слишком длинный
BИндекс привязан к позиции: при вставке/удалении он съезжает и React путает элементы
CИндекс не является числом
DReact запрещает числовые ключи
Поддержать проект