← Назад к вопросам

Для чего нужен атрибут Key при рендеринге списков?

2.0 Middle🔥 171 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Зачем нужен атрибут key при рендеринге списков

key — это специальный атрибут React, который помогает идентифицировать элементы списка. Это один из самых важных инструментов для оптимизации производительности и предотвращения багов.

Как React определяет, какой элемент изменился

Когда React рендерит список без key, он использует позицию элемента в массиве (индекс) для отслеживания:

function UserList() {
  const [users, setUsers] = useState([
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 3, name: "Charlie" }
  ]);

  return users.map((user) => (
    <div>{user.name}</div>  // БЕЗ key
  ));
}

Проблема: если удалить первого пользователя или переставить порядок, React не поймёт, что произошло, и может применить состояние не к тому элементу.

Почему это важно

1. Баг с input и state

function TodoList({ todos }) {
  return todos.map((todo, index) => (
    <div key={index}>  {/* ПЛОХО: использование index как key */}
      <input type="text" defaultValue={todo.text} />
      <button>Удалить</button>
    </div>
  ));
}

// Сценарий:
// 1. todos = ["Купить хлеб", "Помыть посуду", "Убраться"]
// 2. Пользователь вводит текст в первый input: "Купить хлеб - DONE"
// 3. Удаляем первый элемент
// 4. todos = ["Помыть посуду", "Убраться"]
// 5. БАГИ: input всё ещё показывает "Купить хлеб - DONE", потому что React
//    подумал, что это всё ещё первый элемент (индекс 0)

Правильное решение:

function TodoList({ todos }) {
  return todos.map((todo) => (
    <div key={todo.id}>  {/* ХОРОШО: используем уникальный id */}
      <input type="text" defaultValue={todo.text} />
      <button>Удалить</button>
    </div>
  ));
}

2. Потеря состояния компонента

function ListItem({ id, name }) {
  const [isExpanded, setIsExpanded] = useState(false);

  return (
    <div>
      <button onClick={() => setIsExpanded(!isExpanded)}>
        {isExpanded ? "Скрыть" : "Показать"}
      </button>
      {isExpanded && <p>Подробности</p>}
    </div>
  );
}

function App() {
  const [items, setItems] = useState([
    { id: 1, name: "Item A" },
    { id: 2, name: "Item B" }
  ]);

  return (
    <div>
      <button onClick={() => setItems([{ id: 0, name: "New" }, ...items])}>
        Добавить в начало
      </button>
      {items.map((item, index) => (
        <ListItem key={index} id={item.id} name={item.name} />
        {/* БЕЗ правильного key, expanded-состояние сбивается */}
      ))}
    </div>
  );
}

Когда добавляем новый элемент, React теряет track состояния (isExpanded) каждого компонента.

3. Производительность и ненужные ререндеры

function ExpensiveComponent({ id, data }) {
  // Сложные вычисления
  const result = useMemo(() => heavyCalculation(data), [data]);

  return <div>{result}</div>;
}

function App() {
  const [list, setList] = useState([
    { id: 1, data: "A" },
    { id: 2, data: "B" }
  ]);

  // БЕЗ правильного key
  return list.map((item, index) => (
    <ExpensiveComponent key={index} id={item.id} data={item.data} />
  ));
  
  // Когда элемент удаляется, все компоненты ререндерятся,
  // потому что индексы сдвигаются
}

Правильное использование key

Правило 1: Используй уникальный ID

// ХОРОШО
const users = [
  { id: "user-1", name: "Alice" },
  { id: "user-2", name: "Bob" }
];

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

Правило 2: Не используй index как key (если список может изменяться)

// ПЛОХО: если список сортируется, удаляется, добавляется
items.map((item, index) => (
  <Item key={index} item={item} />  // НЕПРАВИЛЬНО
));

// ХОРОШО: если список статичный и никогда не меняется
const staticList = ["Понедельник", "Вторник", "Среда"];
staticList.map((day, index) => (
  <div key={index}>{day}</div>  // OK, потому что список не меняется
));

Правило 3: Key должен быть стабильным

// ПЛОХО: key создается заново каждый рендер
items.map((item) => (
  <Item key={Math.random()} item={item} />  // НЕПРАВИЛЬНО
));

// ПЛОХО: key зависит от данных, которые могут измениться
items.map((item) => (
  <Item key={item.name} item={item} />  // Если name изменится, key"обновится
));

// ХОРОШО: стабильный ID, который не меняется
items.map((item) => (
  <Item key={item.id} item={item} />  // ID стабилен в течение жизни элемента
));

React Reconciliation Algorithm

Вот как React использует key:

// Render 1:
[<Item key="a" />, <Item key="b" />, <Item key="c" />]

// Render 2 (переставили порядок):
[<Item key="c" />, <Item key="a" />, <Item key="b" />]

// БЕЗ key React подумает:
// "Три Item компонента, может быть они те же?"
// И применит props a к c, b к a, c к b (НЕПРАВИЛЬНО)

// С key React поймет:
// "c переместился на первое место, a на второе, b на третье"
// И переместит DOM элементы правильно

Реальный пример: сортировка

function UsersList({ users }) {
  const [sortBy, setSortBy] = useState("name");

  const sorted = [...users].sort((a, b) => {
    if (sortBy === "name") return a.name.localeCompare(b.name);
    return a.id - b.id;
  });

  return (
    <div>
      <button onClick={() => setSortBy("name")}>Сортировать по имени</button>
      <button onClick={() => setSortBy("id")}>Сортировать по ID</button>
      
      {sorted.map((user) => (
        <UserItem key={user.id} user={user} />  {/* ПРАВИЛЬНО */}
      ))}
    </div>
  );
}

// С key={user.id} React правильно переместит элементы DOM
// Без key React может потерять состояние компонентов

Итог

key нужен для:

  • Правильной идентификации элементов списка
  • Сохранения состояния компонентов при изменении списка
  • Оптимизации производительности (React не ререндерит лишние элементы)
  • Избежания багов с потерей данных

Best Practice:

  • Используй уникальный, стабильный ID
  • Избегай Math.random() и индексов
  • Никогда не создавай key динамически

Это одна из самых частых причин багов в React-приложениях, особенно когда список может изменяться.