Пошук уроків, статей та іншого контенту
Додасте стабільні key до елементів списків і зрозумієте, як ключі впливають на оновлення компонентів React.
keyКоли React рендерить список елементів, він має визначити:
який елемент залишився без змін;
який потрібно оновити;
який додали;
який видалили;
який перемістили.
Для цього кожен елемент списку повинен мати стабільний ідентифікатор — проп key.
function UserList({ users }) {
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}У цьому прикладі user.id повідомляє React, що конкретний <li> відповідає конкретному користувачу.
key потрібен не для JavaScript і не для доступу всередині компонента. Це спеціальна інформація для React, яка використовується під час порівняння попереднього і нового дерева елементів.
Розглянемо список:
[
{ id: "a", name: "Анна" },
{ id: "b", name: "Богдан" }
]Після додавання нового елемента на початок:
[
{ id: "c", name: "Софія" },
{ id: "a", name: "Анна" },
{ id: "b", name: "Богдан" }
]Якщо ключами є ідентифікатори, React розуміє, що:
елемент із ключем "c" — новий;
елементи "a" і "b" вже існували;
їх потрібно перемістити, а не створювати заново.
Ключ описує ідентичність елемента, а не його поточну позицію в масиві.
keyКлюч потрібно додавати до елемента, який безпосередньо повертається з map:
function ProductList({ products }) {
return (
<div>
{products.map((product) => (
<article key={product.id}>
<h2>{product.name}</h2>
<p>{product.price} грн</p>
</article>
))}
</div>
);
}Значення ключа має бути:
унікальним серед сусідніх елементів списку;
стабільним між рендерами;
пов’язаним із самими даними.
Зазвичай для цього використовують ідентифікатор із сервера:
<li key={task.id}>{task.title}</li>key не передається як звичайний пропkey — спеціальний проп React. Компонент не може отримати його через props.key.
function Task({ task }) {
console.log(task.key); // undefined
return <li>{task.title}</li>;
}Якщо компоненту потрібен цей ідентифікатор, передайте його окремо:
function Task({ task, taskId }) {
return <li data-task-id={taskId}>{task.title}</li>;
}
function TaskList({ tasks }) {
return (
<ul>
{tasks.map((task) => (
<Task key={task.id} task={task} taskId={task.id} />
))}
</ul>
);
}Тут:
key використовується React;
taskId доступний усередині компонента Task.
Стабільний key допомагає React зберігати стан потрібного компонента після оновлення списку.
У прикладі кожне завдання має власне поле введення:
import { useState } from "react";
const initialTasks = [
{ id: "task-1", title: "Підготувати звіт" },
{ id: "task-2", title: "Перевірити пошту" },
{ id: "task-3", title: "Оновити документацію" },
];
function TaskRow({ task }) {
const [note, setNote] = useState("");
return (
<li>
<strong>{task.title}</strong>
<input
value={note}
onChange={(event) => setNote(event.target.value)}
placeholder="Особиста нотатка"
/>
</li>
);
}
export default function App() {
const [tasks, setTasks] = useState(initialTasks);
function moveLastTaskToStart() {
setTasks((currentTasks) => {
const lastTask = currentTasks[currentTasks.length - 1];
return [lastTask, ...currentTasks.slice(0, -1)];
});
}
return (
<main>
<button onClick={moveLastTaskToStart}>
Перемістити останнє завдання на початок
</button>
<ul>
{tasks.map((task) => (
<TaskRow key={task.id} task={task} />
))}
</ul>
</main>
);
}Якщо ввести нотатку для певного завдання, а потім перемістити завдання, нотатка залишиться разом із цим завданням. React використовує task.id, щоб зберегти ідентичність TaskRow.
Можна написати так:
{tasks.map((task, index) => (
<TaskRow key={index} task={task} />
))}Це працює, якщо список:
ніколи не змінює порядок;
не містить додавання на початок або в середину;
не видаляє елементи;
не фільтрується.
Але для змінюваного списку індекс описує позицію, а не сам елемент.
До змін:
0 → Завдання A
1 → Завдання B
2 → Завдання CПісля видалення першого елемента:
0 → Завдання B
1 → Завдання CReact може вирішити, що компонент із ключем 0 залишився тим самим, хоча тепер він показує вже інше завдання. Це особливо помітно, коли дочірній компонент має:
локальний стан;
поле введення;
стан фокусу;
анімацію;
елементи, які можна редагувати.
Якщо список статичний і порядок гарантовано не змінюється, індекс може бути прийнятним компромісом. Проте ідентифікатор із даних зазвичай є надійнішим варіантом.
Не використовуйте ключі, які змінюються під час кожного рендера:
{tasks.map((task) => (
<TaskRow key={Math.random()} task={task} />
))}Також не слід генерувати новий випадковий ідентифікатор безпосередньо під час рендерингу.
Для React кожне нове випадкове значення означає, що попередній компонент зник, а замість нього з’явився новий. У результаті React може:
втрачати локальний стан;
скидати значення полів;
втрачати фокус;
виконувати зайві операції в DOM.
Ідентифікатор потрібно створити під час створення самого елемента даних і зберігати разом із ним:
const newTask = {
id: crypto.randomUUID(),
title: "Нове завдання",
};Після цього під час рендерингу використовуйте вже збережений id:
<TaskRow key={task.id} task={task} />Ключі повинні бути унікальними серед сусідніх елементів одного списку:
function CategoryList({ categories }) {
return (
<section>
{categories.map((category) => (
<div key={category.id}>
<h2>{category.name}</h2>
<ul>
{category.products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
))}
</section>
);
}Однакові product.id у двох різних категоріях не обов’язково є проблемою, оскільки це різні списки із власними сусідніми елементами.
Натомість дублікати в одному списку є помилкою:
const products = [
{ id: "product-1", name: "Клавіатура" },
{ id: "product-1", name: "Миша" },
];Такі ключі не дають React однозначно визначити елемент. У консолі з’явиться попередження про дублікати ключів, а поведінка оновлення списку може бути некоректною.
key для фрагментів у спискуЯкщо один елемент списку має повертати кілька сусідніх елементів, скористайтеся Fragment із ключем:
import { Fragment } from "react";
function ContactList({ contacts }) {
return (
<dl>
{contacts.map((contact) => (
<Fragment key={contact.id}>
<dt>{contact.name}</dt>
<dd>{contact.email}</dd>
</Fragment>
))}
</dl>
);
}Скорочений запис фрагмента не підтримує key:
{/* Так не можна додати key */}
<>
<dt>{contact.name}</dt>
<dd>{contact.email}</dd>
</>У такому випадку потрібно використати Fragment із явним імпортом.
key може навмисно створити компонент зановоЗміна ключа не лише допомагає React знайти компонент. Вона також сигналізує, що це вже інший компонент.
function Editor({ documentId }) {
return <DocumentForm key={documentId} documentId={documentId} />;
}Коли documentId змінюється, DocumentForm монтується заново. Його локальний стан починається з початкових значень.
Це може бути корисно, якщо форма повністю належить конкретному документу. Але ключ не слід змінювати випадково або використовувати як універсальний спосіб виправлення проблем зі станом.
Помилково:
function TaskList({ tasks }) {
return tasks.map((task) => (
<div>
<TaskRow key={task.id} task={task} />
</div>
));
}Ключ потрібно додати до елемента, який безпосередньо повертається з map:
function TaskList({ tasks }) {
return tasks.map((task) => (
<div key={task.id}>
<TaskRow task={task} />
</div>
));
}Помилково:
<li key={task}>{task.title}</li>Об’єкти не перетворюються на унікальні ключі автоматично. Використовуйте конкретне поле:
<li key={task.id}>{task.title}</li>Назва може повторюватися або змінюватися:
<li key={task.title}>{task.title}</li>Якщо назва не гарантує унікальність і стабільність, краще використовувати task.id.
Попередження про відсутній або дубльований key не варто просто приховувати. Воно означає, що React не отримав достатньо інформації для правильного зіставлення елементів.
Під час рендерингу списку перевірте:
Чи є key на елементі, який повертає map?
Чи походить ключ із самих даних?
Чи залишається він незмінним між рендерами?
Чи унікальний він серед сусідніх елементів?
Чи не використовується індекс у списку, який можна сортувати, фільтрувати, додавати або видаляти?
Чи не очікує компонент отримати key через props?
key допомагає React визначати ідентичність елементів списку.
Ключі потрібні для коректного оновлення, переміщення та видалення елементів.
Найкращий ключ — стабільний унікальний ідентифікатор із даних, наприклад item.id.
Індекс масиву підходить лише для справді статичних списків.
Випадкові ключі спричиняють зайві перемонтування та втрату локального стану.
key є спеціальним пропом і не передається дочірньому компоненту.
Якщо елемент має локальний стан, правильний ключ допомагає зберегти цей стан за потрібним елементом.