React змінився, ваші хуки теж мають змінитися [переклад з англійської]

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

Вашій увазі пропонується переклад статті React Has Changed, Your Hooks Should Too авторства Смітті Брауна. Автор надав дозвіл на публікацію перекладу в коментарях до оригінальної статті. Переклад підготовлений dolgachio для спільноти Open Source UA 🇺🇦.

🙏 Величезна подяка пану undead404 за вичитку і виправлення. Саме цей пан стоїть за оцією імбою koreni.org.ua/.

Примітка: Відповідно до правил української мови англійське слово «hooks» має,бути перекладено, як «гаки» або «гачки», або при застосуванні транслітерації має стати словом «гукс» або «гуки». Термін «хуки» є сталим в україномовному ком’юніті реакт-розробників, тож з вашого дозволу, далі ми будемо використовувати саме його. Ця і інші занудні дискусії зазвичай відбуваються в моєму Threads: @dolgach.io

Хуки React (React hooks) з нами вже протягом багатьох років, але більшість проєктів використовують їх однаково: трішки useState, роздутий useEffect, і купа патернів, які бездумно копіюють з файлу в файл. Ми всі через це проходили.

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

А з появою Concurrent React (ера React 18/19) те, як React обробляє дані, особливо асинхронні дані, змінилося. Тепер у нас є серверні компоненти, use(), серверні дії (server actions), ідіоматичне завантаження даних... І навіть деякі асинхронні можливості всередині клієнтських компонентів, якщо у вас на проєкті це налаштовано.

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

Пастка useEffect: надто багато, надто часто

Хук useEffect найчастіше використовується неправильно. Він часто перетворюється на смітник для логіки, якій там не місце, — наприклад, завантаження даних, розрахунка похідних значень, навіть простого перетворення стану. Саме так компоненти починають поводитися непередбачувано: повторно рендеряться в несподівані моменти, або роблять це частіше, ніж потрібно.

useEffect(() => {
  fetchData();
}, [query]); // Перезапускається при кожній зміні query, навіть коли нове значення по суті є таким же

Більшість цього болю виникає через змішування похідного стану (derived state) та побічних ефектів (side effects), з якими React працює дуже по-різному.

Використання ефектів так, як того хоче React

Правило від React тут неочікувано прямолінійне:

Використовуйте ефекти лише для справжніх побічних ефектів (side effects) — того, що взаємодіє з зовнішнім світом.

Усе інше має обчислюватися під час рендерингу.

const filteredData = useMemo(() => {
  return data.filter(item => item.includes(query));
}, [data, query]);

Якщо вам дійсно потрібен ефект, то useEffectEvent з React ваш справжній друг. Цей хук дає змогу отримати доступ до найсвіжіших значень пропсів та стану зсередини ефекту без роздування масиву залежностей.

const handleSave = useEffectEvent(async () => {
  await saveToServer(formData);
});

Перш ніж писати useEffect, спитайте себе:

  • Чи це потрібно для чогось зовнішнього відносно самого застосунку (мережа, DOM, підписки)?
  • Чи я все таки можу обчислити потрібні значення під час рендерингу?

Якщо друге, то засоби на кшталт useMemo,useCallback, а також примітиви фреймворку зроблять компонент значно менш крихким.

🙋🏻‍♂️ Коротенька примітка

Будь ласка, не ставтеся до useEffectEvent як до хитрощі, що звільняє від масивів залежностей. Цей хук спеціально створений і оптимізований, щоб працювати зсередини ефектів.

Кастомні хуки: не тільки повторне використання, але й інкапсуляція

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

Наприклад, замість того, щоб завалювати компоненти кодом для налаштування по типу:

useEffect(() => {
  const listener = () => setWidth(window.innerWidth);
  window.addEventListener('resize', listener);
  return () => window.removeEventListener('resize', listener);
}, []);

Можна перемістити це все в хук:

function useWindowWidth() {
  const [width, setWidth] = useState(
    typeof window !== 'undefined' ? window.innerWidth : 0
  );

  useEffect(() => {
    const listener = () => setWidth(window.innerWidth);
    window.addEventListener('resize', listener);
    return () => window.removeEventListener('change', listener);
  }, []);

  return width;
}

Виглядає значно охайніше. Простіше тестувати. І з компонентів уже не стирчать назовні деталі імплементації.

👉🏻 Порада щодо SSR

Завжди починайте з детермінованого резервного значення (fallback), щоб уникнути розбіжностей під час гідратації.

Стан на основі підписки з використанням useSyncExternalStore

В React 18 було додано хук useSyncExternalStore, і це само собою вирішує цілий клас помилок, пов’язаних із підписками, розривами зображення (tearing) та високочастотними оновленнями.

Якщо ви колись страждали від matchMedia, позиції прокрутки сторінки чи сторонніх сховищ, котрі поводяться неузгоджено протягом рендерів, — цей API React саме для вас.

Використовуйте його для:

  • Браузерних API (matchMedia, видимості сторінки, позиції прокрутки сторінки)
  • Зовнішніх сховищ стану (Redux, Zustand, кастомних систем підписки)
  • Всього чутливого до швидкодії та керованого подіями
function useMediaQuery(query) {
  return useSyncExternalStore(
    callback => {
      const mql = window.matchMedia(query);
      mql.addEventListener('change', callback);
      return () => mql.removeEventListener('change', callback);
    },
    () => window.matchMedia(query).matches,
    () => false // запасне значення для SSR
  );
}

⚠️ Примітка: useSyncExternalStore забезпечує синхронні оновлення. Це не пряма заміна для useState.

Плавніший UI завдяки переходам і відкладеним значенням

Якщо ваш застосунок здається повільним, коли користувачі друкують або застосовують фільтрування, із цим можуть допомогти інструменти React для керування одночасністю (concurrency). Це не магія, але вони допомагають React надавати пріоритет терміновим оновленням замість дорогих.

const [searchTerm, setSearchTerm] = useState('');
const deferredSearchTerm = useDeferredValue(searchTerm);

const filtered = useMemo(() => {
  return data.filter(item => item.includes(deferredSearchTerm));
}, [data, deferredSearchTerm]);

Введення тексту залишається швидким, тоді як ресурсомісткі операції фільтрації виконуються не одразу, а відкладено.

Стисла ментальна модель:

  • startTransition(() => setState()) → відкладає оновлення стану
  • useDeferredValue(value) → відкладає похідні значення

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

Хуки, які легко тестувати та дебажити

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

  • Не змішуйте бізнес-логіку з UI
  • Тестуйте хуки окремо, де це можливо
  • Виносьте логіку провайдера в окремий хук для більшої ясності
function useAuthProvider() {
  const [user, setUser] = useState(null);
  const login = async credentials => {
    /* ... */
  };
  const logout = () => {
    /* ... */
  };
  return { user, login, logout };
}

const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const value = useAuthProvider();
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export function useAuth() {
  return useContext(AuthContext);
}

Ви подякуєте собі, коли наступного разу доведеться це дебажити.

Не хуками єдиними: на шляху до React-застосунків, орієнтованих на дані

React рухається в напрямку рендерингу, орієнтованого насамперед на дані, особливо з огляду на дозрівання серверних компонентів (Server Components) і патернів, що базуються на діях (actions). Хоча React не прагне забезпечити таку ж деталізовану реактивність, як Solid.js, але робиться значний акцент на асинхронних даних та UI, що керується даними на сервері.

API, про які варто знати:

  • use() для асинхронних ресурсів під час рендерингу (переважно для серверних компонентів; обмежена підтримка в клієнтських компонентах через server actions)
  • useEffectEvent для стабільних зворотних викликів ефектів
  • useActionState для керування асинхронним станом у межах робочих процесів (workflow)
  • Механізми кешування та примітиви роботи з даними на рівні фреймворку
  • Покращені інструменти для параллельного рендерингу та кращі DevTools

Напрямок зрозумілий: React прагне, щоб ми менше покладалися на використання useEffect як «швейцарського ножа», і більше — на очевидні потоки даних, керовані рендером.

Проєктування хуків з огляду на похідний стан (derived state) і меж між сервером і клієнтом робить застосунок природно адаптованим до майбутніх змін.

Хуки як архітектура, а не синтаксис

Хуки — це не просто API, зручніший за класи; це архітектурний патерн.

  • Зберігайте похідний стан на рівні функції рендерингу
  • Використовуйте ефекти лише для справжніх побічних ефектів (side effects).
  • Компонуйте логіку на основі невеликих, вузькоспеціалізованих хуків.
  • Дозвольте засобам асинхронності впорядковувати асинхронні потоки.
  • Не обмежуйте своє мислення межами між клієнтом і сервером.

React еволюціонує. Наші хуки мають розвиватися разом із ним.

І якщо ви досі пишете хуки так само, як у 2020 році — це нормально; так робить більшість із нас. Проте React 18+ пропонує значно кращий набір інструментів, і опанування цих патернів швидко приносить свої плоди.

👍ПодобаєтьсяСподобалось4
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

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

а то у нас Robert Hooke чомусь Гук, але hook чомусь хук

Robert Hooke (/hʊk/)

dictionary.cambridge.org/...​onunciation/english/hooke
dictionary.cambridge.org/...​ronunciation/english/hook

тобто по букві тіпа гуки правильно, але по суті правильно хуки і Роберт Хук і закон Хука

з іншого боку передача [h] в іншій мові може бути близька до г, в залежності що за мова і що за діалект мови.

Тому ерго, [h] завжди буде темою для срачів між табором «ми знавці ноу-хау,
і розтягуємось по закону Хука і читаємо книги про Харі Потера» і табором «ми знавці нов гав, граємо в гандбол біля гідроспоруди, поки дівчата пускають гелієві кульки в небо, де літає гелікоптер» і патаму ета будєт всєгда живітєльнай почвай для майора фсб свєсті іх лбамі, штоб еті хахли опять вцепілісь другдругу в х глоткі

І тому мені насрать, якою буквою, і хто що про це думає, if you know what i mean, стандарт який не який та має бути

всю статтю ще почитаю, але між тим вже дяка