Skip to main content

Що робить хук useSyncExternalStore()?

Просте визначення

useSyncExternalStore() - це хук, який дозволяє React підписуватися на зовнішній стан (тобто стан, що зберігається поза React), і гарантує узгоджене й передбачуване оновлення UI, навіть якщо використовується конкурентний рендеринг або серверний рендеринг (SSR).


Синтаксис

javascript
const snapshot = useSyncExternalStore( subscribe, getSnapshot, getServerSnapshot? // опційно для SSR );
АргументТипПризначення
subscribefn((callback) => unsubscribe)Функція, яка підписує компонент на зміни в зовнішньому сховищі
getSnapshotfn() => anyПовертає поточний стан (snapshot) зі сховища
getServerSnapshot (опційно)fn() => anyВикористовується при серверному рендерингу, щоб отримати стан без доступу до клієнта

Приклад використання

Припустимо, у нас є просте зовнішнє сховище:

javascript
// store.js let listeners = []; let count = 0; export const store = { getSnapshot: () => count, subscribe: (callback) => { listeners.push(callback); return () => { listeners = listeners.filter(l => l !== callback); }; }, increment: () => { count++; listeners.forEach(l => l()); }, };

Тепер підключимо його до React-компонента:

javascript
import { useSyncExternalStore } from 'react'; import { store } from './store'; function Counter() { const count = useSyncExternalStore( store.subscribe, store.getSnapshot ); return ( <div> <p>Count: {count}</p> <button onClick={store.increment}>+</button> </div> ); }

Тепер компонент:

  • читає стан із зовнішнього стора;
  • автоматично перерендерюється, коли store.increment() змінює стан;
  • працює стабільно при concurrent рендерингу і SSR.

Навіщо з'явився цей хук

До React 18 бібліотеки (Redux, Zustand, MobX та ін.) використовували нестабільні підходи на кшталт useEffect або useLayoutEffect для синхронізації із зовнішнім станом.

Це викликало проблеми:

  • При Concurrent Rendering React міг почати рендер із "застарілими" даними;
  • При SSR / Hydration стан клієнта міг не збігтися з серверним → попередження;
  • Підписка могла викликатися в неправильний момент життєвого циклу.

useSyncExternalStore() вирішує всі ці проблеми.


Як це працює під капотом

React гарантує, що:

  1. Під час рендеру React викликає getSnapshot() і використовує його значення.
  2. Якщо під час рендеру зовнішній стан змінюється, React повторює рендер, щоб відобразити актуальні дані.
  3. Після монтування React викликає subscribe(listener) і стежить за змінами.
  4. Коли стан у зовнішньому сторі змінюється, React викликає listener, перевіряє новий getSnapshot(), і якщо він відрізняється, запускає оновлення.

Головне: React робить це синхронно, щоб уникнути "розсинхронізації" між даними і UI.


Приклад з Redux (внутрішньо)

React-Redux v8+ тепер внутрішньо використовує useSyncExternalStore() замість кастомних підписок, щоб гарантувати коректну поведінку в React 18:

javascript
const state = useSyncExternalStore( store.subscribe, store.getState, store.getState // для SSR );

Раніше Redux-хук useSelector покладався на useEffect → могли бути лаги і невідповідності. Тепер усе синхронно і передбачувано.


Приклад з Window Size (зовнішнє джерело - браузер)

javascript
function useWindowWidth() { return useSyncExternalStore( (callback) => { window.addEventListener('resize', callback); return () => window.removeEventListener('resize', callback); }, () => window.innerWidth ); } function Component() { const width = useWindowWidth(); return <div>Ширина вікна: {width}px</div>; }

Навіть якщо React "призупинить" рендер у Concurrent Mode, useSyncExternalStore гарантує, що width завжди відповідає актуальному стану вікна, без "старих" значень або глітчів при гідратації.


Що робить третій аргумент (getServerSnapshot)

Цей аргумент використовується при серверному рендерингу (SSR): він дозволяє задати стан на сервері, якщо клієнтські дані недоступні.

Приклад:

javascript
useSyncExternalStore( store.subscribe, store.getSnapshot, () => 0 // на сервері вважаємо, що count = 0 );

Це запобігає помилкам гідратації:

javascript
Warning: Text content did not match. Server: "0" Client: "5"

Важливо: не плутати з useEffect або useLayoutEffect

ХукКоли оновлюєтьсяПідходить для зовнішнього стану?
useEffectАсинхронно, після рендеруМоже запізнюватися
useLayoutEffectСинхронно після commitКраще, але не SSR-сумісний
useSyncExternalStoreСинхронно в самому React-цикліІдеальний варіант

Підсумок

useSyncExternalStore() - це низькорівневий хук, який React використовує для синхронізації UI із зовнішніми джерелами даних.

Він гарантує:

  • коректну роботу при Concurrent Rendering,
  • безпеку при SSR + Hydration,
  • відсутність "розсинхронізації" між стором і компонентом.

Коротко

ВластивістьЗначення
ПовертаєПоточний стан із зовнішнього джерела
Автоматично підписуєтьсяТак
SSR-суміснийТак
Concurrent-безпечнийТак
Замінює старі патерни (useEffect для підписок)Так
Основне застосуванняRedux, Zustand, MobX, event listeners, кастомні стори

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.