React-ключі визначають ідентичність компонентів
Дізнайтеся, чому ключі визначають ідентичність компонентів React, як нестабільні ключі спричиняють помилки стану та як обирати надійні ключі для списків і форм.
Користувач редагує другий рядок списку, видаляє перший — і раптом у рядку, що залишився, з’являється неправильне значення. Код виглядає звично: перебрати масив, відрендерити компонент, додати key. Але як ключ використано індекс масиву, тож React сприймає позицію як ідентичність. Коли список змінюється, локальний стан може залишитися прив’язаним до позиції, а не до даних.
Ключі часто сприймають як спосіб прибрати попередження в консолі. Насправді вони роблять більше: ключ допомагає React визначити, який відрендерений компонент відповідає якому елементу між рендерами. Від цього залежить, чи збереже React екземпляр компонента та його локальний стан, чи демонтує його й створить новий. Якщо розуміти ключі як ідентичність, запобігати певним незрозумілим помилкам інтерфейсу стає значно легше.
Ключ — частина ідентичності компонента
React узгоджує попереднє дерево з наступним. Для дочірніх елементів він використовує тип, позицію та ключ, щоб зіставити компоненти. Якщо тип і ключ залишаються стабільними, React може повторно використати наявний компонент і зберегти його стан. Якщо ключ змінюється, React трактує елемент як іншу сутність: старий компонент відкидається, а новий монтується.
Тому ключі важливі не лише для ефективності рендерингу. У рядку можуть бути неконтрольоване поле вводу, елемент у фокусі, розгорнуте меню, чернетка або стан дочірнього компонента. Якщо після зміни порядку React пов’яже цей стан не з тим елементом, HTML може залишатися цілком коректним, але інтерфейс показуватиме не ті дані.
Чому індекси масиву не підходять для динамічних списків
Індекс описує поточне місце елемента, а не його ідентичність. Коли елемент вставляють, видаляють, фільтрують або переміщують, індекси інших елементів змінюються. Тоді React може повторно використати компонент на певній позиції для іншого запису й зберегти локальний стан, який належав попередньому запису.
type Row = { id: string; name: string };
function Rows({ rows }: { rows: Row[] }) {
return rows.map((row, index) => (
<EditableRow key={index} row={row} />
));
}Приклад стає ризикованим, щойно рядки можуть змінювати порядок або склад, а EditableRow має власний стан. Для незмінного списку суто презентаційних елементів індекс може бути прийнятним. Проблема не в самому синтаксисі, а в тому, що позицію використовують як ідентичність, хоча вона може змінюватися.
Обирайте стабільний ключ із даних
Віддавайте перевагу стабільному ідентифікатору, що належить запису й не змінюється, коли запис переміщується в інтерфейсі. Ідентифікатори з бази даних — природний варіант. Якщо новий запис ще не має ID від сервера, створіть клієнтський ID під час створення запису — не всередині render — і зберігайте його разом із цим записом.
function Rows({ rows }: { rows: Row[] }) {
return rows.map((row) => (
<EditableRow key={row.id} row={row} />
));
}Ключ має бути унікальним лише серед сусідніх елементів одного списку. Він не мусить бути глобально унікальним у всьому застосунку. Але для того самого логічного елемента ключ має залишатися стабільним між рендерами. Корисна перевірка: якщо елемент переміститься на іншу позицію, чи зможу я й далі отримати той самий ключ безпосередньо з цього елемента?
Поширені варіанти ключів і компроміси
| Варіант ключа | Зазвичай безпечний, коли | Основний ризик |
|---|---|---|
| Постійний ID запису | ID належить елементу й не змінюється | Відсутній або повторно використаний ID може зіставити різні записи |
| ID, згенерований на клієнті | Його створюють один раз і зберігають разом із новим елементом | Створення під час рендеру змінює ідентичність щоразу |
| Індекс масиву | Список статичний і його не можна впорядкувати, доповнити чи скоротити | Стан може прив’язатися до позиції, а не до запису |
| Випадкове значення під час рендеру | Майже ніколи | Кожен рендер монтує елемент заново й відкидає його локальний стан |
Складені ключі можуть стати в пригоді, якщо окреме стабільне поле не є унікальним. Поєднайте стабільні поля, які разом визначають запис, і перевірте, що така комбінація не спричиняє колізій. Не додавайте значення, які змінюються під час редагування: коли ключ змінюється, React монтує компонент заново, що може скинути фокус і незбережений стан поля.
Ключі можуть навмисно скидати стан
Зміна ключа не завжди є помилкою. Це прямий спосіб повідомити React, що компонент тепер має іншу ідентичність і його локальний стан не слід зберігати. Наприклад, редактор профілю може використовувати той самий компонент для різних ідентифікаторів користувачів. Якщо задати ключ редактора на основі ID користувача, чернетка одного користувача не з’явиться під час перегляду профілю іншого.
Робіть це навмисно, а не як універсальне виправлення застарілого стану. Спершу визначте, який компонент володіє станом і яка подія має його скидати. Якщо ключ на основі ID користувача доречний, скидання стану буде явним і пов’язаним зі зміною ідентичності. Якщо ж ключ залежить від значення, яке змінюється під час введення, компонент може монтуватися заново після кожного натискання клавіші.
Ключі важливі у формах і вкладених списках
Динамічні форми особливо чутливі, адже кожен рядок може містити значення, повідомлення про перевірку та стан фокусу. Видалення поля має видалити стан саме цього поля, а не перенести його до наступного. Стабільний ID поля надійніший за його поточну позицію в масиві. Той самий принцип стосується вкладених списків: кожна колекція, яку перебирають, потребує стабільних ключів для власних дочірніх елементів.
Докладніше про передбачувану поведінку великих форм читайте в матеріалі React Forms Without State Sprawl. Якщо в інтерфейсі є кілька взаємопов’язаних станів, стаття React State Machines: Replace Boolean Flags With Explicit UI States пояснює, як зробити переходи зрозумілими. Ці теми пов’язані, але не тотожні: модель стану визначає, що може робити інтерфейс, а ключ допомагає React зберігати стан саме за тим елементом, якому він належить.
Практичний список для перевірки
- Знайдіть кожну колекцію, відрендерену через map, яка може змінитися після першого рендеру.
- Перевірте, чи походить кожен ключ із запису, а не з його поточної позиції.
- Переконайтеся, що той самий логічний запис зберігає той самий ключ після вставлення, видалення, фільтрації або сортування.
- Перевірте унікальність ключів серед сусідніх елементів, зокрема для записів, створених на клієнті.
- Знайдіть ключі, які змінюються під час редагування або щоразу випадково генеруються під час рендеру.
- Перевірте поведінку: відредагуйте рядок, а потім вставте, видаліть, відсортуйте й відфільтруйте сусідні рядки.
Якщо під час тесту стан переходить до неправильного рядка, спочатку перевірте ідентичність, а не додавайте нові ефекти чи копіювання значень в інше сховище стану. Стабільний ключ часто виправляє прив’язку безпосередньо в місці виникнення проблеми. Якщо поведінка не змінилася, перевірте також, чи залишається незмінним тип компонента між рендерами та чи не скидає його стан інший рівень.
Ключі — невелика частина коду React, яка суттєво впливає на поведінку динамічних інтерфейсів. Якщо обирати їх за стабільною ідентичністю предметної сутності, а не за зручною позицією під час рендеру, чернетки, поля вводу, фокус і стан дочірніх компонентів залишатимуться прив’язаними до очікуваних даних. Командам, які створюють насичені даними інтерфейси зі змінними списками й складними формами, WebMriya допомагає розробляти складні вебзастосунки.
Поширені запитання
Чому ризиковано використовувати індекс масиву як ключ React?
Індекси змінюються, коли елементи додають, видаляють або переміщують. Через це React може зберегти стан компонента для іншого запису, а не для того, якому цей стан належав спочатку.
Чи має ключ React бути унікальним у всьому застосунку?
Ні. Ключ має бути унікальним серед сусідніх елементів одного відрендереного списку. Також він має залишатися стабільним для того самого логічного елемента між рендерами.
Чи може зміна ключа скинути стан компонента?
Так. Новий ключ повідомляє React, що компонент має іншу ідентичність, тому React демонтує попередній екземпляр і монтує новий. Це корисно, коли ідентичність справді змінилася, але випадкова зміна ключа може відкинути чернетку або фокус.