Що робить `useSyncExternalStore()`?
Що робить useSyncExternalStore()
useSyncExternalStore() підключає React-компонент до зовнішнього стану,
яким не керує React (наприклад, Redux, Zustand, EventEmitter, кастомний store, window.location тощо).
Він забезпечує:
- синхронне читання поточного значення (знімок store),
- підписку на оновлення,
- коректну роботу з concurrent rendering (React 18) і SSR.
Простіше кажучи:
useSyncExternalStore()дозволяє React-компонентам надійно читати дані із зовнішнього сховища і оновлюватися при їхній зміні.
Синтаксис
const state = useSyncExternalStore(
subscribe, // функція підписки
getSnapshot, // функція читання поточного стану
getServerSnapshot? // (опційно) версія для SSR
);Приклад: підключення до кастомного store
Припустимо, у нас є просте сховище поза React:
// 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:
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.
import { useSelector } from "react-redux";
function MyComponent() {
const count = useSelector((state) => state.counter.value);
return <div>{count}</div>;
}Усередині useSelector реалізовано приблизно так:
function useSelector(selector) {
return useSyncExternalStore(
store.subscribe,
() => selector(store.getState())
);
}Приклад з window.location (зовнішнє джерело)
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-й аргумент:
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);Це важливо, щоб значення на сервері і клієнті збігалося, інакше React видасть попередження про "mismatched hydration".
Коли використовувати
| Коли | Чому |
|---|---|
| Підключаєшся до зовнішнього сховища (Redux, Zustand, кастомний store) | React не керує цим станом |
| Потрібна реактивна підписка | Компонент повинен перерендеритися при зміні store |
| Важливо уникнути розсинхронізації в Concurrent Rendering | useSyncExternalStore гарантує узгодженість |
| Потрібна безпечна поведінка при SSR | Можна передати getServerSnapshot |
Коли не потрібно
| Випадок | Чому |
|---|---|
Ти працюєш зі станом через useState або useReducer | React сам керує даними - 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.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.