Головна / Статті

Що коштує вам використання useEffect для отримання даних та що змінює TanStack Query

Чому отримання даних на основі ефекту призводить до дублювання запитів, шаблонного коду та застарілих функцій у React, і як ключі, параметри staleTime та механізм відкидання даних у TanStack Query вирішують ці проблеми.

Видавець: REACTAPP.TOP6 хв читання1360 слів

Джерела: Stop Using useEffect for API Calls, Use React Query Instead

Отримання даних усередині useEffect — це перший паттерн, який вивчають більшість розробників React, і він добре працює для одного запиту на одній сторінці. У міру зростання додатку цей самий паттерн тихо множить кількість запитів, повторює шаблонний код у кожному компоненті та спричиняє приховані проблеми з залежностями. У цій статті пояснюється, звідки виникають ці проблеми та як TanStack Query (раніше React Query) вирішує кожну з них, щоб ви могли вирішити, коли перейти на інший підхід та що налаштувати після цього.

Паттерн, з яким починають усі

Знайома структура — це ефект із порожнім масивом залежностей, який викликає функцію fetch під час монтування компонента:

useEffect(() => {
  fetchData();
}, []);

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

Чому запит відправляється двічі та яка справжня причина цього

Поширеним першим сюрпризом є те, що під час завантаження сторінки у вкладці мережі можна побачити один і той самий запит двічі. Під час розробки StrictMode у React навмисно завантажує, знімає та знову завантажує компоненти, щоб виявити ефекти, які не очищуються самостійно. Дублювання запиту є навмисним у режимі розробки та не відбувається у продакшн-версії.

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

TanStack Query відокремлює дані від компонента. Результати зберігаються у спільному кеші під ключем запиту. Коли інший компонент або той самий після повторного завантаження запитує ключ, дані якого все ще вважаються актуальними, бібліотека повертає значення з кешу, не вдаючись до мережі. Кілька компонентів, які одночасно запитують той самий ключ, також діляться одним запитом у процесі обробки.

Ручне керування станом завантаження та помилок

За допомогою useEffect кожен запит потребує власної трійки змінних стану для даних, прапорця завантаження та помилки:

const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

Якщо повторювати це для кожного виклику API, результатом буде велика кількість майже ідентичного коду, у кожній копії якого існує ризик помилки. Найпоширеніша — це забути сбросити значення loading у випадку помилки, що залишає індикатор завантаження у робочому стані. Інші проблеми включають непропускання відповідей, які надходять після того, як компонент було видалено з DOM, а також дозвіл повільній ранній відповіді перезаписувати новішу, коли значення введень швидко змінюються.

useQuery безпосередньо повертає data, error, isLoading та isError (разом із більш детальними полями статусу), а також самостійно обробляє випадки неправильного порядку відповідей. Шаблонний код зникає з компонентів, а крайні випадки вирішуються один раз у добре протестованій бібліотеці, а не кілька разів у коді додатку.

Масиви залежностей проти ключів запиту

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

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

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

Кеш, staleTime та анулювання

Три функції пояснюють більшу частину відмінностей у повсякденній роботі.

Кеш

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

staleTime

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

invalidateQueries

Після зміни, такої як оновлення профілю чи створення повідомлення, ви викликаєте queryClient.invalidateQueries із відповідним ключем. Відповідні запити позначаються як застарілі, а ті, що наразі використовуються, автоматично оновлюються. Не потрібно вручну оновлювати дані після кожної зміни чи копіювати відповіді сервера до локального стану.

Разом ці три елементи вирішують більшість проблем, які виникають під час отримання даних на основі ефектів. Щоб детальніше дізнатися про організацію ключів, параметрів запитів та змін у масштабних системах, перегляньте наш посібник зі структуруванням шару даних TanStack Query.

Типова сценарій міграції

Розгляньмо панель керування Next.js SaaS із кількома вкладками, кожна з яких завантажує власні дані за допомогою useEffect. Оскільки зміна вкладок призводить до перезавантаження панелей, кожна зміна вкладки посилає нову хвилю запитів. Бекенд отримує значно більше трафіку, ніж це необхідно, і користувачі помічають повільне завантаження.

Міграція зазвичай включає три кроки:

  1. Замініть кожен ефект на useQuery та ключ, який би фіксував усі параметри запиту.
  2. Встановіть для кожного запиту значення staleTime, яке відображатиме частоту справжніх змін даних.
  3. Після кожної зміни даних скасуйте дію ключів, які впливають на ці дані.

Очікуваним результатом є плавніше функціонування панелі керування, менша кількість запитів до бекенду та кращий досвід користувачів, при цьому код у кожному компоненті залишається скороченим.

Де все ще має застосовуватися useEffect

Це не привід повністю видаляти useEffect. Він залишається правильним інструментом для синхронізації з елементами за межами React, які не є даними у форматі запит-відповідь: підписки, слухачі подій DOM, таймери та віджети сторонніх розробників. Однак для API-запитів у додатку, який розвивається, краще використовувати спеціалізовану бібліотеку для керування станом сервера, оскільки вона зменшує навантаження на бекенд, усуває зайвий код та надає можливості кешування та анулювання даних без додаткових налаштувань.

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

Основні висновки

  • Подвійні запити під час розробки виникають через StrictMode; у продакшені проблема полягає у тому, що запити, засновані на ефектах, повторюються при кожному монтуванні.
  • Ключ запиту замінює масив залежностей: включіть усі дані, від яких залежить запит, і тоді буде забезпечено кешування та повторне отримання даних.
  • Значення staleTime за замовчуванням дорівнює нулю, тому якщо ви хочете уникнути фонових повторних запитів під час перемонтування, встановіть його навмисно.
  • Анулюйте відповідні ключі після змін, замість того щоб вручну оновлювати дані чи синхронізувати стан.
  • Використовуйте useEffect для синхронізації з зовнішніми системами, а не для завантаження даних з сервера.