Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Чому в React потрібен ключ?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)`key` є пропом, який React використовує для ідентифікації елементів списку під час reconciliation. Без нього React порівнює по позиції і перерендерить все після перестановки. Зі стабільними key оновлюються лише змінені вузли. ```jsx // Правильно: стабільний унікальний ID {items.map(item => <li key={item.id}>{item.text}</li>)} // Неправильно: індекс зсувається при перестановці {items.map((item, i) => <li key={i}>{item.text}</li>)} ``` **Головне правило:** якщо список може змінювати порядок, використовуй стабільний ID з даних, а не індекс масиву.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**key** є спеціальним пропом React, який унікально ідентифікує елементи списку, щоб алгоритм reconciliation (узгодження) міг відстежувати додавання, видалення та перестановки без повного перебудування DOM. ## Теорія ### TL;DR - Key схожий на бейдж на конференції: React використовує його, щоб зіставити "старий елемент" з "новим" між рендерами, а не перевіряти всіх за позицією в черзі. - Без key React порівнює по індексу. Будь-яка перестановка змушує оновити кожен елемент після зміни. - Зі стабільними key React зіставляє по ідентичності та оновлює лише те, що реально змінилось. - Правило вибору: статичний список без перестановок, тоді index підійде. Динамічний список із перестановками або локальним станом, тоді потрібен стабільний унікальний ID. ### Короткий приклад ```jsx // Без key: React попереджає і порівнює по позиції const BadList = ({ fruits }) => ( <ul> {fruits.map(fruit => <li>{fruit.name}</li>)} </ul> ); // Зі стабільними key: React відстежує кожен елемент за ідентичністю const GoodList = ({ fruits }) => ( <ul> {fruits.map(fruit => <li key={fruit.id}>{fruit.name}</li>)} </ul> ); ``` Поміняй місцями `fruits[0]` і `fruits[1]`: `BadList` перерендерить усі `<li>` після зміни. `GoodList` просто переставить два вузли. ### Чому порівняння за позицією ламається Reconciliation React будує два дерева (попередній і наступний virtual DOM) і шукає мінімальну кількість DOM-операцій. Для списків без key елементи зіставляються за індексом у масиві. Це працює, поки порядок не змінюється. Додай елемент на початок списку. Одразу кожен індекс зсувається на одиницю. React бачить "нові" елементи на кожній позиції і перерендерить їх усіх. Будь-який стан інпуту, фокус або позиція скролу всередині цих компонентів зникають. Зі стабільними key React спочатку читає ключ і зіставляє старі fiber-вузли з новими за ідентичністю. Монтуються та демонтуються лише справді нові або видалені вузли. ### Коли потрібні стабільні key - Статичний список без перестановок: index підійде. - Динамічний список з додаванням, видаленням або сортуванням: ID з бази даних або інший стабільний ідентифікатор. - Елементи з локальним станом (поля вводу, чекбокси, панелі, що розгортаються): завжди стабільні key, інакше стан перестрибує на неправильний компонент. - Вкладені списки: key на кожному рівні `.map()`, а не лише на зовнішньому. Якось бачив кошик у e-commerce, де кількість товарів скидалась до 1 щоразу, коли сортування після акції переставляло елементи. Фікс зайняв три символи: `key={index}` стало `key={item.variantId}`. ### Типові помилки **Index як key у динамічному списку** ```jsx // Перестановка елементів → стан і фокус стрибають на неправильні компоненти {items.map((item, index) => <li key={index}>{item.text}</li>)} ``` Індекс змінюється разом із масивом. React сприймає кожен елемент після зміни як інший компонент. Фікс: `key={item.id}`. **Неунікальні key** ```jsx // Два користувачі "John" → React бере першого, другий демонтується {users.map(user => <li key={user.name}>{user.name}</li>)} ``` Key мають бути унікальними серед сусідніх елементів. Використовуй справжній ID або композитний ключ: `key={user.id + '-' + user.name}`. **Випадкові key на кожному рендері** ```jsx // Math.random() → новий key кожен рендер → повний remount щоразу {items.map(item => <li key={Math.random()}>{item.text}</li>)} ``` Генеруй стабільні ID один раз під час першого завантаження даних і зберігай у стані. Ніколи не генеруй всередині `.map()`. **Відсутні key у вкладених списках** ```jsx {categories.map(cat => ( <div key={cat.id}> {cat.items.map(item => <span>{item}</span>)} {/* key тут відсутній */} </div> ))} ``` Кожен `.map()` потребує key, а не тільки зовнішній рівень. ### Де використовується - Адмін-дашборди з рядками таблиць: ID рядків як key. - Material-UI DataGrid: проп `getRowId` реалізує ту саму ідею на рівні компонента. - React Native `FlatList`: проп `keyExtractor` виконує ту саму роль. Невірні key призводять до візуальних артефактів під час скролу у віртуалізованому списку. - TanStack Table з нескінченним скролом: стабільні key запобігають появі дублікатів при повторному завантаженні. ### Питання на співбесіді **Q:** Чому не використовувати index завжди? **A:** Для списку, який ніколи не змінює порядок і без стану всередині елементів, index підходить. Щойно з'являється сортування, фільтрація або додавання на початок, index ламає відстеження стану. **Q:** Що робити, якщо в даних немає унікального ID? **A:** Генеруй його за допомогою `crypto.randomUUID()` або бібліотеки uuid під час першого завантаження даних і зберігай у стані. Ніколи не генеруй всередині `.map()`. **Q:** Як key впливає на хуки всередині елементів списку? **A:** Key не змінює порядок виклику хуків. Але якщо key змінився, React демонтує й повторно монтує компонент, скидаючи весь стан хуків. Це корисна техніка: зміна key є найчистішим способом примусово скинути компонент. **Q:** Який алгоритм React використовує для порівняння списків? **A:** Reconciler знаходить найдовшу зростаючу підпослідовність (longest increasing subsequence) key, щоб мінімізувати кількість переміщень. Стабільні відсортовані key дають результат близький до O(n). Повністю перемішані key потрапляють у найгірший випадок. Це можна спостерігати через `getSnapshotBeforeUpdate` під час перестановки. ## Приклади ### Статичний список (index як key допустимий) ```jsx const MENU_ITEMS = ['Home', 'About', 'Contact']; function Nav() { return ( <nav> {MENU_ITEMS.map((label, index) => ( // Статичний, ніколи не переставляється → index тут ок <a key={index} href={`/${label.toLowerCase()}`}>{label}</a> ))} </nav> ); } ``` Список не змінюється, тому індекс не зсувається. Стану, який можна втратити, немає. ### Динамічний список з локальним станом (потрібен стабільний ID) ```jsx function CartList({ lineItems }) { const [notes, setNotes] = React.useState({}); return ( <ul> {lineItems.map(item => ( <li key={item.variantId}> {/* Стабільний SKU ID */} <span>{item.name}</span> <input value={notes[item.variantId] || ''} onChange={e => setNotes({ ...notes, [item.variantId]: e.target.value })} placeholder="Додати нотатку" /> </li> ))} </ul> ); } ``` Коли кошик переставляє елементи після акційного сортування, кожен інпут зберігає нотатку для свого товару. З `key={index}` нотатка третього товару опинилась би на першому після сортування.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.