Всем привет! В этой статье я хочу на простом примере рассказать о том, как синхронизировать состояние redux-приложений между несколькими клиентами и сервером, что может оказаться полезным при разработке realtime-приложений.  В качестве примера мы будем разрабатывать список покупок, с возможностью изменять позиции с любого устройства в реальном времени, по следующим требованиям: Дабы обойтись минимальными трудозатратами, не будем делать UI для доступа ко всем спискам, а просто будем различать их по идентификатору в URL. Можно заметить, что приложение в таком виде с точки зрения клиентского функционала и UI мало чем будет отличаться от знаменитого todo-списка. Поэтому статья в большей мере будет посвящена клиент-серверному взаимодействию, сохранению и обработке состояния на сервере. В планах у меня также написание следующей статьи, где на примере этого же приложения мы рассмотрим, как сохранять redux-состояние в DynamoDB и выкатывать упакованное в Docker приложение в AWS. Для создания среды разработки воспользуемся замечательным инструментом create-react-app. Создавать прототипы с ним довольно легко: он подготовит все необходимое для продуктивной разработки: webpack c hot-reload, начальный набор файлов, jest-тесты. Можно было бы настроить это все самостоятельно для большего контроля над процессом сборки, но в данном приложении это не принципиально. Придумываем название для нашего приложения и создаем его, передав в качестве аргумента в create-react-app: create-react-app создал нам некоторую структуру проекта, но на самом деле для его корректной работы необходимо лишь, чтобы файл ./src/index.js являлся точкой входа. Поскольку же наш проект подразумевает использование как клиента, так и сервера, то изменим начальную структуру на следующую: Добавим так же в package.json команду для старта сервера node ./src/server.js Состояние приложения redux хранит в так называемом store в виде javascript-объекта любого типа. При этом любое изменение состояния должен проводить reducer — чистая функция, на вход которой подается текущее состояние и action (тоже javascript-объект). Возвращает же она новое состояние с изменениями. Мы можем использовать нашу клиентскую логику для списка покупок, которую реализует reducer, как на стороне браузера, так и в node.js окружении. Таким образом мы сможем хранить состояние списка независимо от клиента и сохранять его в БД. Для работы с сервером будем использовать давно уже ставшую стандартом для работы с websockets библиотеку socket.io. Для каждого списка заведем свой room и будем посылать каждый action тем пользователям, которые находятся в этой же комнате. Помимо этого, для каждой комнаты на сервере будем хранить свой store с состоянием для этого списка. Синхронизация клиентов с сервером будет происходить следующим образом:  То есть каждый раз когда происходит какой-либо action, то: В основном в redux-мире взаимодействие с сервером происходит по http, через библиотеки вроде redux-thunk или redux-saga, которые позволяют добавить немного асинхронности и сайд-эффектов в синхронный, чистый мир redux. Хотя нам не требуется такого рода связь с сервером, мы тоже будем использовать redux-saga на клиенте, но только для одной задачи: перенаправления на только что созданный список в случае, если в URL нет идентификатора. Не буду заострять особого внимания на инициализации, необходимой для работы redux, это хорошо описано в официальной документации redux, скажу лишь, что нам необходимо зарегистрировать два middleware: из упомянутого пакета redux-saga и наш emitterMiddleware. Первый нужен для редиректа, как уже было упомянуто, а последний мы напишем для синхронизации экшенов с сервером через socket.io-client. Создадим файл ./src/client/action-emitter.js в котором и будет реализация упомянутого emitterMiddleware: Как я уже упоминал, мы будем использовать на клиенте redux-saga, чтобы при первом заходе местоположение клиента менялось на соответствующее списку, который мог быть создан только что. Незамысловатым образом в ./src/client/modules/products-list/saga/index.js опишем сагу, реагирующую на получение списка продуктов и комнаты, в которой находится клиент: Входной точкой для сервера будет добавленный в скрипты в package.json ./src/server.js: Стоит обратить внимание, что при старте нашего сервера ему передается клиентский reducer: это необходимо для того, чтобы сервер тоже мог поддерживать актуальное состояние списков, получая только действия, а не все состояние целиком. Заглянем в ./src/server/bootstrap.js: Приступим к специфичной для нашего сервера логике и опишем действия, которые он должен поддерживать: Все эти действия я предлагаю также описать с помощью redux и для этого сделать модуль ./src/server/modules/room-service, содержащий соответствующие saga и reducer. Там же мы сделаем простейшее хранилище для наших комнатных store ./src/server/modules/room-service/data/in-memory.js: При событии socket-сервера будем просто делать dispatch c соответствующим action из модуля room-service на серверном store. Опишем это в ./src/server/connection-handler.js: Оставим обработку userJoin и userLeft на совести пытливого читателя, не поленившегося заглянуть в репозиторий, а сами посмотрим на то, как обрабатывается dispatchClientAction. Как мы помним, необходимо сделать два действия: За первое отвечает генератор ./src/server/modules/room-service/saga/dispatch-to-room.js: Он же кладет следующий action модуля room-service — emitClientAction, на который реагирует ./src/server/modules/room-service/saga/emit-action.js: Вот таким незамысловатым образом экшены попадают на остальных клиентов в комнате. В простоте, на мой взгляд, и кроется прелесть full-stack redux, а также мощь переиспользования клиентской логики для воспроизведения состояния на сервере и других клиентах. Хоть немного сумбурно, но мой рассказ подходит к концу. Я не акцентировал внимание на вещах, о которых уже есть множество статей и уроков (в любом случае, полный код приложения можно посмотреть в репозитории), а остановился на том, о чем информации, на мой взгляд, меньше. Так что если остались какие-то вопросы — прошу в комменты. ASP.NET + React/Redux developer