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

Для чего React просит указывать ключ при рендере массива?

2.0 Middle🔥 191 комментариев
#React#Архитектура и паттерны

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

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

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

Для чего React просит указывать ключ при рендере массива?

Ключ (key) в React — это критично важный аспект оптимизации производительности и сохранения состояния компонентов. Это одна из наиболее частых ошибок, которые делают разработчики.

Что такое ключ?

Ключ — это уникальный идентификатор элемента в списке, который помогает React определить, какой элемент изменился, был добавлен или удален.

// НЕПРАВИЛЬНО - без ключа
{items.map((item) => (
  <div>{item.name}</div>
))}

// ПРАВИЛЬНО - с ключом
{items.map((item) => (
  <div key={item.id}>{item.name}</div>
))}

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

1. Сохранение состояния компонентов

function ListItem({ item }) {
  const [checked, setChecked] = useState(false);

  return (
    <div>
      <input
        type="checkbox"
        checked={checked}
        onChange={(e) => setChecked(e.target.checked)}
      />
      {item.name}
    </div>
  );
}

function List() {
  const [items, setItems] = useState([
    { id: 1, name: 'Элемент 1' },
    { id: 2, name: 'Элемент 2' },
    { id: 3, name: 'Элемент 3' }
  ]);

  return (
    <div>
      {items.map((item) => (
        // НЕПРАВИЛЬНО - key={index}
        <ListItem key={item.id} item={item} />
      ))}
    </div>
  );
}

Проблема без правильного ключа:

  • Пользователь чекает первый элемент
  • Мы удаляем первый элемент из массива
  • React думает, что это просто переиндексирование
  • State остается, но применяется к другому элементу!
  • Второй элемент вдруг окажется checked!

2. Производительность

// ПЛОХО - key={index}
function UserList() {
  const [users, setUsers] = useState([
    { id: 1, name: 'Иван' },
    { id: 2, name: 'Мария' },
    { id: 3, name: 'Петр' }
  ]);

  const addUserAtStart = () => {
    setUsers([{ id: 0, name: 'Новый' }, ...users]);
  };

  return (
    <div>
      <button onClick={addUserAtStart}>Добавить в начало</button>
      {users.map((user, index) => (
        // key={index} НЕПРАВИЛЬНО!
        <UserProfile key={index} user={user} />
      ))}
    </div>
  );
}

// Что происходит:
// Было: key=0 (Иван), key=1 (Мария), key=2 (Петр)
// Добавили в начало: Новый
// Стало: key=0 (Новый), key=1 (Иван), key=2 (Мария), key=3 (Петр)
// React перерисовывает ВСЕ элементы!
// ХОРОШО - key={id}
function UserList() {
  const [users, setUsers] = useState([
    { id: 1, name: 'Иван' },
    { id: 2, name: 'Мария' },
    { id: 3, name: 'Петр' }
  ]);

  const addUserAtStart = () => {
    setUsers([{ id: 0, name: 'Новый' }, ...users]);
  };

  return (
    <div>
      <button onClick={addUserAtStart}>Добавить в начало</button>
      {users.map((user) => (
        <UserProfile key={user.id} user={user} />
      ))}
    </div>
  );
}

// Что происходит:
// Было: key=1 (Иван), key=2 (Мария), key=3 (Петр)
// Добавили в начало: Новый
// Стало: key=0 (Новый), key=1 (Иван), key=2 (Мария), key=3 (Петр)
// React добавляет ТОЛЬКО новый элемент!

Когда ключ особенно критичен

1. Фильтрация и сортировка

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Купить молоко', done: false },
    { id: 2, text: 'Написать код', done: false },
    { id: 3, text: 'Поужинать', done: false }
  ]);

  const [filter, setFilter] = useState('all');

  const filtered = todos.filter((todo) => {
    if (filter === 'done') return todo.done;
    if (filter === 'pending') return !todo.done;
    return true;
  });

  return (
    <div>
      <button onClick={() => setFilter('all')}>Все</button>
      <button onClick={() => setFilter('done')}>Готовые</button>
      <button onClick={() => setFilter('pending')}>Ожидают</button>

      {filtered.map((todo) => (
        // key={todo.id} ОБЯЗАТЕЛЕН!
        // иначе state input смешается при фильтрации
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </div>
  );
}

2. Анимированные списки

function AnimatedList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        // Без ключа анимация запустится на всех элементах!
        <motion.li
          key={item.id}
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
        >
          {item.name}
        </motion.li>
      ))}
    </ul>
  );
}

3. Статефул компоненты в списке

function CounterList() {
  const [items, setItems] = useState([
    { id: 1, count: 0 },
    { id: 2, count: 0 },
    { id: 3, count: 0 }
  ]);

  function Counter({ item }) {
    const [count, setCount] = useState(item.count);

    return (
      <div>
        {item.id}: {count}
        <button onClick={() => setCount(count + 1)}>+</button>
      </div>
    );
  }

  return (
    <div>
      {items.map((item) => (
        // key={item.id} КРИТИЧЕН!
        // Иначе state счетчиков будет смешиваться
        <Counter key={item.id} item={item} />
      ))}
    </div>
  );
}

Как React использует ключи

// Без ключа - по индексу элемента
const elements = [
  <div key="0">А</div>,
  <div key="1">Б</div>,
  <div key="2">В</div>
];

// Если изменить порядок или удалить:
const elements = [
  <div key="0">Б</div>,
  <div key="1">В</div>
];
// React видит key="0" и key="1", но содержимое изменилось!

// С правильными ключами:
const elements = [
  <div key="A">А</div>,
  <div key="B">Б</div>,
  <div key="C">В</div>
];

// После переупорядочивания:
const elements = [
  <div key="B">Б</div>,
  <div key="C">В</div>
];
// React видит, что key="A" удален, и удаляет element A
// key="B" и key="C" переместились, но это ОН элементы

Правила выбора ключей

Хорошие ключи:

// 1. ID элемента (ЛУЧШЕ)
{items.map((item) => (
  <div key={item.id}>{item.name}</div>
))}

// 2. UUID если нет ID
{items.map((item) => (
  <div key={item.uuid}>{item.name}</div>
))}

// 3. Комбинированный ключ
{items.map((item) => (
  <div key={`${item.userId}-${item.postId}`}>
    {item.content}
  </div>
))}

Плохие ключи:

// 1. НИКОГДА - index как ключ
{items.map((item, index) => (
  <div key={index}>{item.name}</div> // ПЛОХО!
))}

// 2. НИКОГДА - Math.random()
{items.map((item) => (
  <div key={Math.random()}>{item.name}</div> // УЖАСНО!
))}

// 3. Избегать - нестабильные вычисленные ключи
{items.map((item) => (
  <div key={`item-${item.name}`}>
    {item.name}
  </div>
))} // Если name меняется, ключ меняется!

Практический пример: правильное использование

function App() {
  const [todos, setTodos] = useState([
    { id: 1, title: 'Задача 1', completed: false },
    { id: 2, title: 'Задача 2', completed: false }
  ]);

  const addTodo = () => {
    const newTodo = {
      id: Date.now(),
      title: `Новая задача ${todos.length + 1}`,
      completed: false
    };
    setTodos([...todos, newTodo]);
  };

  const deleteTodo = (id) => {
    setTodos(todos.filter(t => t.id !== id));
  };

  const toggleTodo = (id) => {
    setTodos(todos.map(t =>
      t.id === id ? { ...t, completed: !t.completed } : t
    ));
  };

  return (
    <div>
      <button onClick={addTodo}>Добавить задачу</button>
      <ul>
        {todos.map((todo) => (
          // ПРАВИЛЬНО: key={todo.id}
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            {todo.title}
            <button onClick={() => deleteTodo(todo.id)}>
              Удалить
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

ESLint правило

// .eslintrc.js
{
  rules: {
    "react/jsx-key": "error" // Ошибка если ключей нет
  }
}

Выводы

  1. Всегда используй ключи при рендере массивов
  2. Используй стабильные, уникальные значения (ID, UUID)
  3. Избегай index как ключа - это приводит к ошибкам
  4. Не используй Math.random() - каждый рендер новый ключ
  5. Ключ помогает React идентифицировать, какой элемент какой
  6. Сохраняет состояние - input values, toggles, счетчики
  7. Оптимизирует производительность - минимизирует перерисовку

Правильное использование ключей — это одна из основ эффективной разработки на React.

Для чего React просит указывать ключ при рендере массива? | PrepBro