Что такое hook useSyncExternalStore?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разбор хука useSyncExternalStore: решение для интеграции внешних сторов в React 18
useSyncExternalStore — это новый хук, представленный в React 18 в рамках Concurrent Features. Он предназначен для безопасного и эффективного чтения данных из внешних источников состояния (external stores) в функциональных компонентах. Его главная цель — решить проблемы, возникающие при использовании традиционных методов подписки (например, через useEffect) в условиях конкурентного рендеринга (concurrent rendering).
Ключевая проблема, которую решает хук
До React 18 разработчики часто интегрировали внешние сторы (Redux, Zustand, MobX или даже нативный EventEmitter) следующим образом:
import { useState, useEffect } from 'react';
function useLegacySubscription(store) {
const [state, setState] = useState(store.getState());
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setState(store.getState());
});
return unsubscribe;
}, [store]);
return state;
}
Этот подход имеет критический недостаток в режиме конкурентного рендеринга: между вызовом store.subscribe() и установкой подписки в useEffect может произойти "гонка состояний" (tearing). Компонент может "увидеть" разные данные во время одного рендера, если внешний стор обновится в момент отрисовки дерева компонентов. Это приводит к неконсистентному UI.
Сигнатура и принцип работы хука
Хук имеет следующую сигнатуру:
const state = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?);
subscribe: (callback: () => void) => () => void— функция, которая подписывает компонент на обновления стора. Должна возвращать функцию отписки.getSnapshot: () => State— функция, возвращающая текущий снимок (snapshot) данных из стора. Она должна быть идентичной (referentially stable) между рендерами, если данные не изменились. React будет вызывать её для определения, изменилось ли состояние.getServerSnapshot: () => State(опционально) — функция, возвращающая начальный снимок данных при рендеринге на сервере (SSR) или гидратации на клиенте.
Принцип работы:
- React вызывает
getSnapshot()в начале рендера, чтобы получить текущее состояние. - Одновременно React активирует подписку через
subscribe. Если стор обновится, React принудительно повторяет рендер компонента. - При новом рендере
getSnapshot()вызывается снова, и её результат сравнивается с предыдущим. Если он изменился, React перерисовывает компонент.
Практический пример использования
Рассмотрим интеграцию с гипотетическим внешним стором, например, состоянием браузера или кастомным event-based стором.
import { useSyncExternalStore } from 'react';
// Внешний стор (например, нативный EventTarget)
const mediaQueryStore = {
subscribe: (callback) => {
const mediaQuery = window.matchMedia('(max-width: 768px)');
const listener = (event) => callback();
mediaQuery.addEventListener('change', listener);
return () => mediaQuery.removeEventListener('change', listener);
},
getSnapshot: () => window.matchMedia('(max-width: 768px)').matches,
getServerSnapshot: () => false // По умолчанию на сервере не мобильный
};
function MobileIndicator() {
const isMobile = useSyncExternalStore(
mediaQueryStore.subscribe,
mediaQueryStore.getSnapshot,
mediaQueryStore.getServerSnapshot
);
return <div>Mobile mode: {isMobile ? 'ON' : 'OFF'}</div>;
}
Преимущества useSyncExternalStore
- Гарантия консистентности: предотвращает tearing в Concurrent React, обеспечивая, что все компоненты, использующие один и тот же стор, увидят одинаковые данные в рамках одного рендера.
- Оптимизация производительности: React управляет подписками централизованно, что может уменьшить количество лишних ре-рендеров по сравнению с кастомными
useEffect+useState. - Поддержка SSR: наличие
getServerSnapshotобеспечивает корректную работу при серверном рендеринге и предотвращает ошибки гидратации. - Стандартизация: предоставляет единый паттерн для работы с любым внешним стором, что улучшает согласованность кода в проекте.
Когда следует использовать этот хук?
Вы НЕ должны использовать useSyncExternalStore напрямую в прикладном коде. Вместо этого:
- Библиотеки состояний (как Redux Toolkit, Zustand, MobX) используют его внутри себя для создания своих хуков (
useSelector,useStore). - Разработчики UI-библиотек или сложных интеграций могут использовать его для подписки на нативные API браузера (как в примере с Media Query) или WebSockets.
Если вы используете современную библиотеку состояния, обновлённую для React 18, вы уже косвенно пользуетесь преимуществами useSyncExternalStore. Например, Redux Toolkit начиная с версии 8.0 автоматически применяет этот хук в своём хуке useSelector.
Вывод
useSyncExternalStore — это низкоуровневый API React, который решает сложную проблему синхронизации с внешними, нереактивными источниками данных в условиях конкурентного рендеринга. Он обеспечивает стабильность, консистентность и производительность, выступая фундаментом для адаптации библиотек управления состоянием к новой архитектуре React. Для большинства разработчиков его использование будет скрыто за абстракциями любимых стейт-менеджеров, но понимание его работы необходимо для отладки сложных сценариев и создания собственных устойчивых интеграций.