Гид по технологиям

Синхронизация Redux store между вкладками с помощью redux-state-sync

7 min read Веб-разработка Обновлено 28 Nov 2025
Синхронизация Redux между вкладками
Синхронизация Redux между вкладками

Иллюстрация логотипа Redux

Быстрые ссылки

  • Обзор
  • Синхронизация хранилища
  • Настройка синхронизации
  • Интеграция с Redux-Persist
  • Критерии приёмки
  • Чек-листы ролей
  • Тесты и сценарии
  • Когда это не подходит
  • Итог

Обзор

redux-state-sync — это библиотека, предоставляющая middleware для Redux, которое пересылает диспатченные действия между вкладками одного origin. Каждый набор вкладок подключается к каналу (Broadcast Channel или запасной механизм), и любые действия, прошедшие через middleware, транслируются другим вкладкам, где они тоже диспатчатся в локальный store.

Ключевые идеи в одной строке:

  • Middleware перехватывает действие и отправляет его в межвкладочный канал.
  • Каждая вкладка запускает слушатель сообщений и, получив действие, диспатчит его в свой store.
  • По умолчанию используется Broadcast Channel API; если он недоступен, библиотека падает на IndexedDB или LocalStorage.

Когда применять:

  • Синхронизация логина/логаута, тем оформления, уведомлений, счётчиков непрочитанных сообщений.
  • Удобно для одностраничных приложений, где пользователь может открыть несколько вкладок одновременно.

Ограничения:

  • Передаваемые данные должны поддерживаться алгоритмом структурного клонирования браузера (structured clone). Сложные объекты (с методами, ссылками на DOM, нестандартными классами) могут быть не полностью восстановлены.

Важно: библиотека не автоматически переносит существующее состояние одной вкладки в новую вкладку — для этого есть дополнительная функция initStateWithPrevTab().

Установка и быстрая настройка

Установите пакет через npm или yarn:

npm install redux-state-sync

Простой пример store до добавления синхронизации:

import {createStore} from "redux";

const state = {authenticated: false};

const reducer = (state, action) => ({...state, ...action});

const store = createStore(reducer, state);

Добавим middleware и инициализируем слушатель сообщений:

import {createStore, applyMiddleware} from "redux";
import {createStateSyncMiddleware, initMessageListener} from "redux-state-sync";

const reduxStateSyncConfig = {};

const reducer = (state, action) => ({...state, ...action});

const store = createStore(
  reducer,
  state,
  applyMiddleware(createStateSyncMiddleware(reduxStateSyncConfig))
);

initMessageListener(store);

Теперь, если открыть сайт в двух вкладках и задиспатчить действие в одной, оно появится и во второй вкладке. В DevTools можно увидеть входящее действие и изменение состояния.

Если вы хотите, чтобы новая вкладка сразу инициализировала состояние из уже открытой вкладки, используйте initStateWithPrevTab():

const store = createStore(reducer, state, applyMiddleware(createStateSyncMiddleware({})));

initStateWithPrevTab(store);

initStateWithPrevTab заменит локальный state новой вкладки состоянием из предыдущей вкладки, если такая найдена.

Настройка синхронизации

createStateSyncMiddleware принимает объект конфигурации с параметрами, которые позволяют тонко управлять поведением пересылки действий. Ниже — наиболее полезные опции с примерами.

Исключение действий

Если у вас есть действия, которые не должны синхронизироваться (например, открытие модального окна локально), используйте blacklist:

const config = {
  blacklist: ["DEMO_ACTION"]
};

// Это действие не будет синхронизировано с другими вкладками
store.dispatch({type: "DEMO_ACTION"});

Альтернативно, примените whitelist чтобы синхронизировать только явно указанные действия:

const config = {
  whitelist: ["LOGIN_SUCCESS", "LOGOUT", "SET_THEME"]
};

Точное фильтрование действий

Если вам нужно правило более гибкое, используйте predicate. Это функция, которая получает действие и возвращает true/false для разрешения синхронизации:

const config = {
  predicate: action => action && action.type && action.meta && action.meta.sync !== false
};

Пример выше синхронизирует только действия, у которых есть meta.sync !== false.

Настройки Broadcast Channel

По умолчанию используется канал с именем redux_state_sync. Вы можете изменить его и передать опции адаптера:

const config = {
  channel: "my_app_channel",
  broadcastChannelOption: {type: "idb"}
};

Параметр broadcastChannelOption передаётся библиотеке pubkey/broadcast-channel и позволяет жестко задать используемую технологию (например, IndexedDB) даже при наличии нативного Broadcast Channel.

Интеграция с redux-persist

Если вы используете Redux Persist, не вызывайте initStateWithPrevTab — достаточно initMessageListener, потому что persisted state уже восстановится из хранилища при загрузке вкладки.

Рекомендуется исключить действия lifecycle Redux Persist из синхронизации:

const config = {
  blacklist: ["persist/PERSIST", "persist/REHYDRATE"]
};

Практические советы по дизайну действий

  • Передавайте в действия только сериализуемые данные (plain objects, массивы, числа, строки, Blob). Избегайте функций и экземпляров классов.
  • Для больших полезных нагрузок (payload) рассмотрите хранение тяжелых данных в IndexedDB и передачу по каналу только идентификатора/сигнала для загрузки.
  • Явно указывайте в meta, какие действия должны быть синхронизированы, чтобы избежать случайной рассинхронизации.

Примеры конфигураций и сниппеты

Минимальная конфигурация:

import {createStateSyncMiddleware, initMessageListener} from "redux-state-sync";

const config = {};
const store = createStore(reducer, applyMiddleware(createStateSyncMiddleware(config)));
initMessageListener(store);

Конфигурация с whitelist и кастомным каналом:

const config = {
  whitelist: ["LOGIN_SUCCESS", "LOGOUT", "SET_PREFS"],
  channel: "my_app_redux_channel"
};

Конфигурация, заставляющая использовать IndexedDB через адаптер:

const config = {
  broadcastChannelOption: {type: "idb"}
};

Критерии приёмки

  1. При диспатче действия X в вкладке A действие X появляется в DevTools и в store вкладки B (если вкладка B открыта).
  2. При логине в одной вкладке пользователь помечается как authenticated во всех открытых вкладках.
  3. Исключённые в blacklist действия не распространяются в другие вкладки.
  4. В случае использования initStateWithPrevTab новая вкладка получает состояние от уже открытой вкладки.
  5. В случаях отсутствия Broadcast Channel синхронизация продолжает работать через IndexedDB/LocalStorage.

Тесты и сценарии приёмки

  • Функциональные тесты:

    • Открыть две вкладки, выполнить LOGIN → убедиться, что оба store отмечают authenticated: true.
    • Диспатчить blacklist-действие → убедиться, что оно не приходит во вторую вкладку.
    • Переключить сеть (offline) → диспатч в другой вкладке; при возврате онлайн проверить состояние.
  • Интеграционные тесты:

    • С поддержкой Redux Persist: перезагрузить вкладку и убедиться, что persisted state корректно загружается и не конфликтует с приходящими сообщениями.
  • Нагрузочные тесты:

    • Отправить серию быстрых действий и проверить, что не возникает гонок, и что приемник корректно применяет их в ожидаемом порядке.

Рольовые чек-листы

Разработчик:

  • Добавить middleware и initMessageListener.
  • Определить whitelist/blacklist и predicate.
  • Убедиться, что actions сериализуемы.

QA инженер:

  • Подготовить тест-кейсы по критериям приёмки и авто-тесты.
  • Проверить поведение при отсутствии Broadcast Channel (эмуляция в браузере/среде).

DevOps:

  • Проверить, что политики безопасности CSP не блокируют механизм хранения (IndexedDB/LocalStorage).
  • Убедиться, что сборки включают библиотеку и не минимизируют её так, чтобы сломать интеграцию.

Product менеджер:

  • Решить, какие доменные действия надо синхронизировать и почему.
  • Протоколировать ожидаемое поведение для UX (например, при логине/логауте).

Планы отката и обработка инцидента

Если после внедрения синхронизации наблюдаются эффекты рассинхронивания или некорректные состояния:

  1. Быстрый откат: убрать middleware из продовой сборки и развёрнуть фикс.
  2. Диагностика: включить логирование действий, приходящих через канал, и сравнить с локальными диспатчами.
  3. Временное решение: применить whitelist, чтобы синхронизировать только безопасные действия, пока не найдена причина.

Когда это не подходит

  • Если ваше приложение опирается на несерилизуемые объекты или сложные экземпляры классов в action.payload.
  • Если требуется строгая атомарная синхронизация большого и сложного состояния (в этом случае лучше реализовать централизованное серверное решение или использовать WebSocket синхронизацию с сервером).
  • Если поведение приложения подразумевает, что некоторые действия должны оставаться строго локальными и не быть видимыми другим вкладкам (например, временные UI-состояния без смысла для других вкладок).

Совместимость и тонкие моменты

  • Broadcast Channel поддерживается в современных браузерах; старые версии могут полагаться на IndexedDB или LocalStorage. Всегда тестируйте в целевых браузерах.
  • Structured clone не передаёт функции и прототипы классов. При необходимости используйте сериализацию в payload (JSON) и явное восстановление на стороне получателя.
  • При использовании SSR (server-side rendering) убедитесь, что никто из серверных процессов не пытается инициализировать слушатель межвкладочного канала.

Риски и смягчения

Риск: рассинхронизация из-за нестабильного порядка сообщений или долгой доставки. Митигирование: добавить в payload метку времени и версию состояния; на получателе применять логику idempotency и проверять версию.

Риск: утечка приватных данных через межвкладочный канал. Митигирование: никогда не включать в action личные токены или чувствительные поля; передавать только сигналы, а при необходимости — краткие индикаторы.

Мини-методология внедрения

  1. Проанализировать доменные действия и выбрать whitelist/blacklist.
  2. Внедрить middleware в dev-ветке и покрыть автоматизированными тестами.
  3. Прогнать QA-скрипты в целевых браузерах и на мобильных устройствах.
  4. Постепенно выкатывать в production и мониторить логи.

Шпаргалка конфигурации (cheat sheet)

  • По умолчанию: channel = “redux_state_sync”; broadcastChannelOption = undefined
  • Принудительный IndexedDB: broadcastChannelOption: {type: “idb”}
  • Исключение Persist действий: blacklist: [“persist/PERSIST”,”persist/REHYDRATE”]
  • Синхронизация только важных действий: whitelist: [“LOGIN_SUCCESS”,”LOGOUT”,”SET_THEME”]

Примеры тестов/приёмки (специфичные кейсы)

  1. Тест: логин в вкладке A → вкладка B получает действие LOGIN_SUCCESS → state.authenticated === true.
  2. Тест: диспатч модального открытия DEMO_ACTION в вкладке A → вкладка B не получает DEMO_ACTION (при blacklist).
  3. Тест: открыть вкладку C после вкладки A и вызвать initStateWithPrevTab → C получает состояние A.

Безопасность и приватность

  • Не отправляйте через межвкладочный канал секреты: JWT, refresh токены или персональные данные. Эти данные должны храниться в защищённых хранилищах и не пересылаться между вкладками.
  • GDPR/приватность: если вы передаёте персональные идентификаторы, убедитесь, что это соответствует политике обработки данных. По возможности передавайте только неизбыточные индикаторы.

Миграция и совместимость версий

  • При обновлении redux-state-sync проверяйте релиз-ноты на изменения API createStateSyncMiddleware и initMessageListener.
  • Тестируйте поведение в режиме с несколькими версионируемыми вкладками — если старые вкладки остаются с предыдущей версией, может возникнуть несовместимость формата сообщений.

Мероприятие принятия решения (диаграмма)

flowchart TD
  A[Нужно ли синхронизировать действие?] -->|Нет| B[Оставить локальным]
  A -->|Да| C{Данные сериализуемы?}
  C -->|Нет| D[Использовать серверную синхронизацию или IndexedDB]
  C -->|Да| E{Нужно ли сразу передавать полное состояние новой вкладке?}
  E -->|Да| F[Использовать initStateWithPrevTab]
  E -->|Нет| G[Передавать отдельные действия через redux-state-sync]

1-строчный глоссарий

  • Broadcast Channel: браузерный API для обмена сообщениями между вкладками одного origin.
  • initMessageListener: функция, регистрирующая слушатель межвкладочных сообщений для вашего store.
  • createStateSyncMiddleware: middleware, которое транспонирует действия в межвкладочный канал.

Итог

redux-state-sync — это недорогой по интеграции инструмент для решения типичных UX-проблем, когда у пользователя открыто несколько вкладок сайта. Он позволяет синхронизировать полезные действия (логин, логаут, изменения настроек) без сложной инфраструктуры. Важно правильно выбрать набор синхронизируемых действий, убедиться в сериализуемости payload и протестировать поведение в целевых браузерах.

Короткие рекомендации:

  • Начните с whitelist для ключевых событий.
  • Исключите lifecycle-действия Redux Persist.
  • Не отправляйте чувствительные данные через канал; при необходимости передавайте только идентификаторы и сигналы.

Полезная деталь: минифицированная библиотека redux-state-sync занимает ~19 КБ, и для базовой синхронизации достаточно нескольких строк кода.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Запуск DOS‑игры на iPad через iDOS 3
Гайды

Запуск DOS‑игры на iPad через iDOS 3

Ошибка: принтер используется другим компьютером — решение
Windows

Ошибка: принтер используется другим компьютером — решение

Искривление текста в Photoshop и Illustrator
Дизайн

Искривление текста в Photoshop и Illustrator

Payday 2 VR не работает — как исправить
Игры

Payday 2 VR не работает — как исправить

Эмулятор HoloLens — запуск и управление приложениями
Дополненная реальность

Эмулятор HoloLens — запуск и управление приложениями

Как зашифровать облачный диск с BoxCryptor
Безопасность

Как зашифровать облачный диск с BoxCryptor