- Опубліковано
- 4 хв читання
Ключі в React
- Автори
- Ім’я
- Learna

Ключі в React
Навіщо потрібен key, коли ми виводимо масив як список елементів?
За ключем React розпізнає той самий елемент серед його сусідів після оновлення списку.
Без цього легко переплутати записи: якщо додати рядок на початок, його поле введення
може успадкувати текст, який користувач набирав зовсім для іншого запису.
Як обрати ключ
- Беріть ID із даних. Часто це вже готовий ідентифікатор запису з бази.
- Ключ має бути унікальним серед сусідніх елементів. У різних списках однакові ключі допустимі.
- Не змінюйте ключ, поки це той самий запис. Якщо під час кожного рендерингу викликати
Math.random(), React щоразу створюватиме піддерево заново, втрачаючи попередній стан і DOM-вузли. - Індекс масиву позначає місце у списку, а не сам запис. Він може підійти для незмінного списку, але створить проблеми, якщо рядки можна додавати, видаляти чи переставляти.
Що відбувається з ключами-індексами
Залишимо createElement із початкового прикладу й додамо поля введення,
щоб побачити помилку на практиці. Виведіть компонент ListWithoutKeys у своєму
React-застосунку. Впишіть нотатку в поле Item #0 й натисніть Add to top.
import { createElement, useRef, useState } from 'react';
function ListWithoutKeys() {
const [list, setList] = useState([{ id: 0, label: 'Item #0' }]);
const nextId = useRef(1);
function prepend() {
// Create identity once, when data is inserted, not during rendering.
const id = nextId.current++;
setList((previous) => [{ id, label: `Item #${id}` }, ...previous]);
}
return createElement(
'section',
null,
createElement(
'ul',
null,
list.map((item, index) =>
createElement(
'li',
{ key: index },
createElement(
'label',
null,
item.label,
createElement('input', {
'aria-label': `Note for ${item.label}`,
defaultValue: '',
}),
),
),
),
),
createElement('button', { type: 'button', onClick: prepend }, 'Add to top'),
);
}

Перший рядок тепер називається Item #1, але в його полі залишилася нотатка для Item #0.
React зберіг поле на позиції з ключем 0, хоча тепер тут інший запис.
А Item #0 перемістився на індекс 1 і отримав інше поле. Це вже помилка в даних:
текст користувача опинився біля чужого запису.
Прив’яжіть ключ до запису
Замініть індекс на item.id. У цьому демо ID створює локальний лічильник.
У справжньому списку краще використати постійний ID, який уже є в даних.
Якщо запис створюється на клієнті, призначайте йому ID один раз — під час додавання.
Не генеруйте новий ID усередині map під час рендерингу.
import { createElement, useRef, useState } from 'react';
function ListWithKeys() {
const [list, setList] = useState([{ id: 0, label: 'Item #0' }]);
const nextId = useRef(1);
function prepend() {
// Create identity once, when data is inserted, not during rendering.
const id = nextId.current++;
setList((previous) => [{ id, label: `Item #${id}` }, ...previous]);
}
return createElement(
'section',
null,
createElement(
'ul',
null,
list.map((item) =>
createElement(
'li',
{ key: item.id },
createElement(
'label',
null,
item.label,
createElement('input', {
'aria-label': `Note for ${item.label}`,
defaultValue: '',
}),
),
),
),
),
createElement('button', { type: 'button', onClick: prepend }, 'Add to top'),
);
}

Повторіть ті самі дії. Новий рядок отримає порожнє поле, а біля Item #0 залишиться те саме поле з вашою нотаткою. Позиції записів змінилися, але їхні ключі — ні.
Це не означає, що React викличе лише функцію нового компонента. Повторний рендеринг, зміни в DOM і монтування заново — різні речі. Коли батьківський компонент оновлюється, React може знову викликати дочірні компоненти, зберігши їхній стан і DOM-вузли. Мемоізація — окрема тема оптимізації. Ключі насамперед потрібні, щоб React не плутав елементи.
Навіщо React потрібні ключі?
Уявіть, що файли на робочому столі можна називати лише за порядком: перший, другий, третій. Видалили перший — і всі назви зсунулися. З постійними іменами такої плутанини немає. Ключі виконують схожу роль у списку сусідніх елементів. В іншому списку ті самі ключі можна використати знову. Водночас React враховує тип компонента й його місце в дереві: самого ключа недостатньо, щоб зберегти стан після перенесення компонента до іншого батька.
Коли стан треба скинути навмисно
Іноді компонент варто створити заново. Скажімо, під час перемикання профілів форма має починатися з даних нового профілю, а не з незбережених змін попереднього:
<ProfileEditor key={selectedProfile.id} profile={selectedProfile} />
Це фрагмент для вбудовування у ваш застосунок: компонент ProfileEditor і змінна
selectedProfile мають бути визначені в ньому. Коли ID зміниться, React змонтує редактор
заново й скине його локальний стан. Використовуйте це свідомо, щоб випадково не втратити
незбережені зміни під час кожного рендерингу.
key обробляє сам React — компонент не отримає його як звичайний проп.
Якщо компоненту потрібен ID, передайте ще й окремий проп id.
useId призначений для зв’язування атрибутів доступності, а не для створення ключів списку.
Якщо одному запису відповідає кілька елементів без спільної обгортки, використайте
повний запис Fragment із ключем. Короткому запису <>...</> передати key не можна.
Перевірте самі
- Виведіть один із повних прикладів у локальному React-проєкті.
- Введіть нотатку й додайте рядок на початок. Подивіться, біля якого запису лишився текст.
- Повторіть зі стабільними ID: нотатка має залишитися біля Item #0.
Початковий приклад у CodeSandbox можна відкрити окремо. Сторонній iframe більше не завантажується разом зі статтею.