← Назад к вопросам

Что такое hook useSyncExternalStore?

2.0 Middle🔥 261 комментариев
#React#Архитектура и паттерны

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разбор хука 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) или гидратации на клиенте.

Принцип работы:

  1. React вызывает getSnapshot() в начале рендера, чтобы получить текущее состояние.
  2. Одновременно React активирует подписку через subscribe. Если стор обновится, React принудительно повторяет рендер компонента.
  3. При новом рендере 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. Для большинства разработчиков его использование будет скрыто за абстракциями любимых стейт-менеджеров, но понимание его работы необходимо для отладки сложных сценариев и создания собственных устойчивых интеграций.

Что такое hook useSyncExternalStore? | PrepBro