Skip to main content

Що робить `useSyncExternalStore()`?

Що робить useSyncExternalStore()

useSyncExternalStore() підключає React-компонент до зовнішнього стану, яким не керує React (наприклад, Redux, Zustand, EventEmitter, кастомний store, window.location тощо).

Він забезпечує:

  • синхронне читання поточного значення (знімок store),
  • підписку на оновлення,
  • коректну роботу з concurrent rendering (React 18) і SSR.

Простіше кажучи: useSyncExternalStore() дозволяє React-компонентам надійно читати дані із зовнішнього сховища і оновлюватися при їхній зміні.


Синтаксис

javascript
const state = useSyncExternalStore( subscribe, // функція підписки getSnapshot, // функція читання поточного стану getServerSnapshot? // (опційно) версія для SSR );

Приклад: підключення до кастомного store

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

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

Тепер підключимо його до React через useSyncExternalStore:

javascript
import { useSyncExternalStore } from "react"; import { subscribe, getSnapshot, increment } from "./store"; function Counter() { const count = useSyncExternalStore(subscribe, getSnapshot); return ( <div> <p>Лічильник: {count}</p> <button onClick={increment}>+1</button> </div> ); }

Тепер:

  • Компонент одразу отримує актуальне значення з getSnapshot();
  • При зміні store викликається subscribe() -> React перерендерює компонент;
  • Усе працює синхронно і передбачувано, навіть у concurrent-режимі.

Що робить кожен аргумент

АргументЩо робить
subscribe(listener)Реєструє колбек, який викликається при зміні стану; повертає функцію відписки
getSnapshot()Повертає поточне значення стану із зовнішнього джерела
getServerSnapshot() (опційно)Версія getSnapshot для SSR, щоб сервер і клієнт були синхронізовані

Чому це з'явилося в React 18

Раніше для цього використовували кастомні хуки на кшталт useStore, useSelector тощо. Але з появою Concurrent Rendering (паралельний рендер) React почав рендерити кілька версій UI "одночасно".

Проблема старих рішень:

  • Вони могли отримати "старе" значення зі store,
  • Або спричинити "розсинхронізацію" (UI показував не те, що реально в store).

useSyncExternalStore вирішує це, тому що:

  • Гарантує, що React завжди читає поточний знімок стану,
  • І оновлюється синхронно, якщо store змінюється під час рендеру.

Приклад: інтеграція з Redux

Redux 8+ тепер внутрішньо використовує useSyncExternalStore.

javascript
import { useSelector } from "react-redux"; function MyComponent() { const count = useSelector((state) => state.counter.value); return <div>{count}</div>; }

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

javascript
function useSelector(selector) { return useSyncExternalStore( store.subscribe, () => selector(store.getState()) ); }

Приклад з window.location (зовнішнє джерело)

javascript
function useLocation() { return useSyncExternalStore( (callback) => { window.addEventListener("popstate", callback); return () => window.removeEventListener("popstate", callback); }, () => window.location.pathname ); } function RouterView() { const path = useLocation(); return <div>Поточний шлях: {path}</div>; }

React автоматично оновить компонент, коли зміниться window.location.


SSR-версія (getServerSnapshot)

При серверному рендерингу (Next.js, Remix тощо) можна передати 3-й аргумент:

javascript
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);

Це важливо, щоб значення на сервері і клієнті збігалося, інакше React видасть попередження про "mismatched hydration".


Коли використовувати

КолиЧому
Підключаєшся до зовнішнього сховища (Redux, Zustand, кастомний store)React не керує цим станом
Потрібна реактивна підпискаКомпонент повинен перерендеритися при зміні store
Важливо уникнути розсинхронізації в Concurrent RenderinguseSyncExternalStore гарантує узгодженість
Потрібна безпечна поведінка при SSRМожна передати getServerSnapshot

Коли не потрібно

ВипадокЧому
Ти працюєш зі станом через useState або useReducerReact сам керує даними - useSyncExternalStore не потрібен
Ти робиш API-запитиВикористовуй useEffect / React Query
Сховище локальне, не глобальнеНадлишково

Підсумок

ПитанняВідповідь
Що робить useSyncExternalStore()Дозволяє React-компоненту підписатися на зовнішній стан (store) і отримувати актуальні дані
Аргументи(subscribe, getSnapshot, getServerSnapshot?)
Навіщо потрібенДля узгодженості стану при Concurrent Rendering і SSR
Де використовуєтьсяRedux, Zustand, Jotai, кастомні store, event emitters
Не використовуватиДля локального React-стану (useState, useReducer)

Головна ідея:

useSyncExternalStore() - це "офіційний спосіб" синхронно підключити React до зовнішніх станів без ризику розсинхронізації між store і UI.

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

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

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