Головна / Статті / Двадцять запитань для інтерв’ю з React, які відрізняють просте використання від справжнього розуміння

Двадцять запитань для інтерв’ю з React, які відрізняють просте використання від справжнього розуміння

Віртуальний DOM, ключі, ефекти, мемоїзація, контекст, SSR та гідратація — пояснення з урахуванням тих складнощів, які насправді досліджують інтерв’юери, а не з текстових визначень з підручників.

2102 слів

Розробка на React та пояснення принципів роботи React — це різні навички. На співбесідах перевіряють саме другу навичку: що відбувається під час виконання setState, чому важливі ключі, коли знову виконуються ефекти. Нижчезазначені двадцять запитань постійно зустрічаються. Відповіді ґрунтуються на проблемах, які вирішує кожна функція, та на нюансах, які виникають у продакшені — а не на заучених формулюваннях з підручників.

1. Що насправді є Віртуальним DOM

Віртуальний DOM — це не якась містична порошинка прискорення. Це звичайне дерево JavaScript, яке описує, як має виглядати інтерфейс користувача. При зміні стану React створює нове віртуальне дерево, порівнює його з попереднім та синхронізує, застосовуючи до браузерного DOM лише необхідні зміни. Робота з реальним DOM запускає процеси формування макету та відображення контенту; зміна об’єктів JavaScript є дешевою операцією, тому React використовує процесор у пам’яті, щоб мінімізувати навантаження на браузер. Такий підхід спрямований на зменшення навантаження на браузер, а не на те, щоб кожне порівняння було безкоштовним. Твердження «Віртуальний DOM завжди швидший» без цього нюансу є поширеною помилкою серед початківців.

2. Віртуальний DOM проти браузерного DOM

Справжні оновлення DOM є ресурсоємкими та можуть спричинити зміну форматування та перемальовування елементів. Віртуальні оновлення — це різниці об’єктів у пам’яті; React групує дорогі операції запису в DOM замість того, щоб виконувати їх щоразу під час зміни стану. Використання функції track-changes для редагування краще, ніж переписувати весь документ через кожну помилку.

3. Чому ключі списку мають значення під час переміщення рядків

Ключі ідентифікують елементи між різними відображеннями. Без них React використовує позицію індексу, що призводить до проблем під час додавання, видалення або переупорядкування елементів. Використання індексу масиву як ключа „функціонує“ доти, доки переупорядкування не призводить до того, що поля введення та прапорці залишаються приєднаними до неправильних рядків — це фальшива „утечка стану“, яка насправді є помилкою ключа. Краще використовувати стабільні унікальні ID з даних; індекси підходять лише для справді статичних списків. Якщо продукт дозволяє переміщувати елементи шляхом перетягування або фільтрувати їх, використання індексів як ключів зрештою пошкодить локальний стан рядків.

4. Пояснення useState з основних принципів

Звичайні елементи інтерфейсу вмирають, коли функція повертається. useState надає функційному компоненту стабільну пам’ять та заплановує перерендеринг, коли ця пам’ять змінюється:

const [count, setCount] = useState(0);

count — це поточне значення; setCount запитує про оновлення. Оновлення не застосовується під час перерендерингу: запис count відразу після setCount все одно показує попереднє значення, оскільки нове значення з’являється під час наступного перерендерингу.

5. Чому оновлення через useState здаються затриманими або групованими

React об’єднує оновлення, які надходять під час однієї події, у один перерендеринг. З версії React 18 це об’єднання стосується також обіцянок та таймерів, а не лише обробників подій React. Якщо вам потрібне останнє значення на основі попереднього стану, використовуйте функціональний оновлювач:

setCount(prev => prev + 1);

Ця форма використовує найсвіжішу значення з черги, а не застарілий знімок стану. Інтерв’юери часто запитують, що станеться, якщо ви закрити змінну count у межах таймауту без форми оновлення.

6. Яку проблему насправді вирішує useEffect?

Функція рендерингу має бути чистою функцією, яка перетворює параметри та стан у JSX. Програми також завантажують дані, підключають слухачі подій, запускають таймери та впливають на DOM — це побічні ефекти. useEffect виконує цю нечисту роботу після збереження змін, а не під час рендерингу:

useEffect(() => {
  const id = setInterval(() => console.log("tick"), 1000);
  return () => clearInterval(id); // cleanup
}, []);

Функція очищення, яка повертається, виконується перед наступним ефектом та під час зняття компонента з DOM. Якщо її пропустити, інтерв’юери запитають про дублікати таймерів та слухачів, які продовжують працювати після зняття компонента.

7. Що насправді контролює масив залежностей?

Він повідомляє React, коли потрібно знову виконати ефект, шляхом поверхневих порівнянь:

  • [] — один раз після монтування
  • [count] — знову, коли змінюється count
  • не вказано — після кожного оновлення (рідко бажано)

Якщо забути про значення, на яке посилається масив, виникає застаріла замикання: ефект завжди зберігає перше зафіксоване значення. Існують правила перевірки на повну залежність саме через те, що такі помилки дуже поширені.

8. Контрольовані та неконтрольовані компоненти

Контрольовані елементи вводу отримують value зі стану React та оновлюються через onChange; правда належить React.

Неконтрольовані елементи вводу зберігають стан DOM; їх читають через ref за потреби (часто під час надсилання даних).

// Controlled
<input value={name} onChange={e => setName(e.target.value)} />
// Uncontrolled
<input ref={inputRef} defaultValue="Dev" />

Режим контролю увімкнуто дозволяє перевірку та форматування в реальному часі, але це коштує переробки кожної натиснутої клавіші. Режим без контролю залишається легшим, коли потрібна лише кінцева значення. Можливі змішані форми — деякі поля контрольовані, інші ні — але їх складніше аналізувати під час перевірок.

9. Передача пропсів та коли зупинитися

Передача пропсів передає дані через шари, які їх лише пересилають далі. Перейменування впливає на багато файлів. Контекст корисний для теми, автентифікації чи локалі; Redux або Zustand допомагають, коли графи стану стають складними. Нюанс: контекст не є безкоштовним — кожен споживач переробляє вміст, коли змінюється значення — тому він не є стандартом для кожного спільного поля. Передача пропсів на два рівні часто є зрозумілішою, ніж створення постачальника контексту для одноразового значення.

10. Коли варто використовувати useReducer замість useState?

Використовуйте useReducer, коли оновлення відбуваються за типом дії, наступний стан залежить від попереднього у складний спосіб, або кілька полів змінюються одночасно:

function reducer(state, action) {
  switch (action.type) {
    case "increment": return { count: state.count + 1 };
    case "reset": return { count: 0 };
    default: return state;
  }
}
const [state, dispatch] = useReducer(reducer, { count: 0 });

Це своєрідний міні-Redux всередині компонента. Булевий перемикач зберігається за допомогою useState; складна логіка переходу має бути в одному тестованому редюсері. Виведення цієї логіки з обробників подій JSX також робить юніт-тести простими, оскільки не потрібно відображати всю структуру.

11. Поясніть відмінності useMemo та useCallback, не просто цитуючи документацію

Обидва інструменти уникають зайвої роботи під час перерендерингу для різних форм даних:

  • useMemo зберігає в кеші обчислений значення
  • useCallback зберігає в кеші посилання на функцію
const sorted = useMemo(() => expensiveSort(list), [list]);
const handleClick = useCallback(() => doThing(id), [id]);

Ідентичність функції має значення, оскільки кожне оновлення створює новий об’єкт функції. Передача нової функції до дочірнього елемента з memo скасовує ефект мемоізації; useCallback зберігає стабільну посилання. Не використовуйте ці хуки скрізь — мемоізація має свою вартість. Використовуйте їх для обчислень із високими витратами часу або для мемоізованих дочірніх елементів. Надмірна мемоізація є поширеною причиною помилок у початківців, яку інтерв’юери люблять підкреслювати.

12. React.memo є корисним — але його легко обійти

React.memo пропускає повторне оновлення, якщо параметри є поверхнево ідентичними. Нові літерали об’єктів чи масивів вважаються різними навіть за ідентичного вмісту, тому використання { style: { color: 'red' } } робить memo марним, якщо батьківські елементи не стабілізують параметри за допомогою useMemo/useCallback. Інакше memo додає витрати на порівняння, не запобігаючи виконанню обчислень у дочірньому елементі.

13. Ключі як ідентифікатори, а не просто попередження консолі

Ключі є системою ідентифікації у React між переробками. Неправильні ключі призводять до використання неправильного елемента DOM для неправильних даних: стан форми залишається у іншому рядку, анімації відтворюються у неправильному елементі, а функція useState для елемента списку зберігає значення попереднього елемента. Це виглядає як пошкодження стану, але насправді це помилка з ключами. Демонстрація несправної лістингу з ключами в сандбоксі — один із найшвидших способів засвоїти це правило.

14. Контекст: правильні та неправильні використання

Контекст дозволяє обмінюватися значеннями, необхідними багатьом компонентам, без передачі параметрів — автентифікований користувач, тема, локалізація:

const ThemeContext = createContext();
<ThemeContext.Provider value={theme}>
  <App />
</ThemeContext.Provider>

Це погано підходить для високочастотних станів (значення форми за кожним натисканням клавіші) у великому дереві, оскільки кожен користувач оновлює дані при кожній зміні без вибіркової підписки. Краще використовувати сховище з більш деталізованими підписками для такої структури. Переключення тем є класичним прикладом хорошого відповідності контексту; положення курсора в спільному редакторі зазвичай таким не є.

15. Відповідність життєвих циклів класів ефектам

Приблизне відповідність класів:

  • componentDidMount → useEffect(..., [])
  • componentDidUpdate → useEffect(..., [dep])
  • componentWillUnmount → очищення ефекту

Глибша зміна: життєві цикли орієнтовані на час (завантаження/оновлення/видалення); ефекти — на синхронізацію — підтримання відповідності цієї зовнішньої системи цим значенням, саме тому ефекти виконуються знову, коли змінюються залежності. Обробка ефектів як методів життєвого циклу, перекладених рядок за рядком, є причиною того, чому люди починають боротися з масивом залежностей.

16. Стан порівняно з пропсами

Пропси — це лише для читання дані від батьківського елемента. Стан — це дані, якими володіє компонент та які змушують його перероблятися при зміні. Пропси налаштовують компонент ззовні; стан — це те, що він пам’ятає про себе. label кнопки є пропсом; те, чи вона призупинена під час обробки запиту, є станом. Плутанина цих двох понять призводить до антипатернів, таких як спроби змінювати пропси або піднесення тимчасових флагів інтерфейсу занадто високо.

17. Виявлення непотрібних переробок без здогадок

Поширені причини: батьки передають нові літерали об’єктів/масивів/функцій, постійна зміна контексту при перерендерингу всіх користувачів, або стан знаходиться на занадто високому рівні. Не вгадуйте — скористайтеся профілером React DevTools, зафіксуйте взаємодію та перегляньте, які пропси змінилися. Часто для вирішення проблеми стан переміщують нижче або ділять компоненти, щоб дорогі піддрощі більше не впливали на дешеві оновлення, а не спочатку обгортають усе useMemo. Профілери перетворюють відчуття повільності на конкретне пояснення проблеми з боку батьківських компонентів чи пропсів, яке можна виправити.

18. SSR порівняно з CSR

CSR надсилає легку HTML-шкаралупу разом із JS; браузер створює сторінку після завантаження — швидко для обслуговування, повільніше для реального відображення, історично слабкіший з точки зору SEO доки не запуститься JS. SSR надсилає HTML за кожним запитом, а потім гідратує його, додаючи слухачі — краще перше відображення та SEO, але більше роботи на сервері. Next.js та подібні інструменти додають статичне генерування та стрімінг, але основний компроміс залишається між часом відповіді сервера/SEO та витратами та складністю сервера. Твердження «SSR завжди кращий», не вказавши цього компромісу, є слабкою відповіддю під час співбесіди.

19. Невідповідності під час гідратації та причини їх виникнення

Хайдратація приєднує React до серверного HTML без викидання маркапу. Невідповідності виникають, коли серверний HTML відрізняється від першого оформлення на клієнті: Date.now() або Math.random() у процесі оформлення, перевірки window, які відрізняються на сервері, або додавання вузлів через розширення. React гучно попереджає та часто переформатовує вміст на клієнті для виправлення ситуації — це додаткова робота та видимий момент неправильного вмісту. Зазвичай для запобігання цьому використовується патерн захисту API, доступних лише в браузері, за допомогою useEffect або компонентів з функціями.

20. Чому React обгортає нативні події у SyntheticEvent

Клас SyntheticEvent у React нормалізує особливості різних браузерів (щоб onChange працював послідовно) та історично використовував делегування подій на рівні кореня замість одного нативного слухача на кожному вузлі. У React 17+ делегування відбувається до контейнера кореня додатку замість document, але ідея залишається незмінною. Раніше використовувалося об’єднання об’єктів подій для їх повторного використання, тож асинхронний доступ міг повертати значення null; ця практика скасована з React 17, проте наявність шару між подією браузера та обробником все ще вказує на глибину ієрархії. Згадка про те, що e.nativeEvent все ще існує, свідчить про те, що ви розумієте: ця абстракція є обгорткою, а не заміною моделі подій DOM.

Що насправді цінують під час співбесід

Чіткі відповіді під час інтерв’ю пояснюють проблему, її сутність та спосіб допомоги колезі — а не просто заучену визначення. Інтерв’юери менше цікавляться тим, чи можете ви дати визначення useEffect, а більше — чи не завдали вам проблем старі замикання, відсутність очищення чи помилки залежностей, та чи можете ви пояснити причини. Перед інтерв’ю відтворіть ці помилки у сандбоксі; досвід, отриманий унаслідок їх вирішення, свідчить про розуміння теми. Визначення допомагають пройти першу хвилину розмови; компроміси та історії невдач ведуть решту діалогу.

Тримайте короткий особистий посібник із описом багів, які ви виправили — проблеми з ефектами, некоректні клавіші, неузгодженості з водним балансом — та практикуйтеся пояснювати кожен з них протягом менше хвилини. Така підготовка краща за швидке запам’ятовування сигнатур API напередодні, і вона дозволяє наводити конкретні приклади, коли співбесідник просить про приклад з вашої роботи. Пов’язуйте кожен приклад із виправленням, яке ви впровадили, щоб відповідь ґрунтувалася не лише на проблемах, а й на рішеннях. Саме цей крок відрізняє тих, хто лише читає документацію, від тих, хто ефективно працює з цим фреймворком під тиском.