Как справиться со сложными побочными эффектами в Redux?

Я часами пытался найти решение этой проблемы ...

Я занимаюсь разработкой игры с онлайн-табло. Игрок может войти и выйти в любое время. После окончания игры, игрок увидит табло и увидит свой рейтинг, и результат будет представлен автоматически.

Табло показывает рейтинг игрока и таблицу лидеров.

Табло очков используется как тогда, когда пользователь заканчивает играть (чтобы отправить счет), так и когда пользователь просто хочет проверить свой рейтинг.

Вот где логика становится очень сложной:

Если пользователь вошел в систему, то оценка будет представлена ​​в первую очередь. После сохранения новой записи будет загружено табло.

В противном случае табло будет загружено немедленно. Игроку будет предоставлена ​​возможность войти или зарегистрироваться. После этого счет будет отправлен, а затем табло будет обновлено снова.

Однако, если нет оценки для отправки (просто просмотр таблицы рекордов). В этом случае существующая запись проигрывателя просто загружается. Но поскольку это действие не влияет на табло, необходимо одновременно загружать и табло, и запись игрока.

Существует неограниченное количество уровней. Каждый уровень имеет свое табло. Когда пользователь просматривает табло, он «наблюдает» за этим табло. Когда он закрыт, пользователь перестает наблюдать за ним.

Пользователь может войти и выйти в любое время. Если пользователь выходит из системы, рейтинг пользователя должен исчезнуть, а если пользователь входит в систему как другая учетная запись, то информация о рейтинге для этой учетной записи должна быть выбрана и отображена.

... но получение этой информации должно происходить только для табло, чей пользователь в настоящее время наблюдает.

Для операций просмотра результаты должны кэшироваться в памяти, так что если пользователь повторно подписывается на то же табло, выборки не будет. Однако, если есть оценка, кеш не должен использоваться.

Любая из этих сетевых операций может быть неудачной, и игрок должен быть в состоянии повторить их.

Эти операции должны быть атомарными. Все состояния должны обновляться за один раз (без промежуточных состояний).

В настоящее время я могу решить эту проблему с помощью Bacon.js (функциональной библиотеки реактивного программирования), так как она поставляется с поддержкой атомарного обновления. Код довольно лаконичен, но сейчас это беспорядочный непредсказуемый код спагетти.

Я начал смотреть на Redux. Поэтому я попытался структурировать хранилище и придумал что-то вроде этого (в синтаксисе YAMLish):

user: (user information)
record:
  level1:
    status: (loading / completed / error)
    data:   (record data)
    error:  (error / null)
scoreboard:
  level1:
    status: (loading / completed / error)
    data:
      - (record data)
      - (record data)
      - (record data)
    error:  (error / null)

Проблема становится: куда я положу побочные эффекты.

Для действий без побочных эффектов это становится очень легким. Например, наLOGOUT действие,record Редуктор может просто уничтожить все записи.

Тем не менее, некоторые действия имеют побочный эффект. Например, если я не вошел в систему перед отправкой счета, то я вхожу в систему,SET_USER действие сохраняет пользователя в магазине.

Но потому что у меня есть счет, чтобы представить, этоSET_USER действие должно также вызвать запуск запроса AJAX, и в то же время установитьrecord.levelN.status вloading.

Вопрос в том:как я могу показать, что побочные эффекты (представление баллов)должно происходить при входе в систему атомарным способом?

В архитектуре Elm средство обновления также может создавать побочные эффекты при использовании формыAction -> Model -> (Model, Effects Action), но в Redux это просто(State, Action) -> State.

ОтАсинхронные действия Документы, способ, которым они рекомендуют, состоит в том, чтобы поместить их в создателя действия. Означает ли это, что для успешного входа в систему необходимо будет ввести логику отправки оценки в создатель действия?

function login (options) {
  return (dispatch) => {
    service.login(options).then(user => dispatch(setUser(user)))
  }
}

function setUser (user) {
  return (dispatch, getState) => {
    dispatch({ type: 'SET_USER', user })
    let scoreboards = getObservedScoreboards(getState())
    for (let scoreboard of scoreboards) {
      service.loadUserRanking(scoreboard.level)
    }
  }
}

Я нахожу это немного странным, потому что код, ответственный за эту цепную реакцию, теперь существует в 2 местах:

В редукторе. когдаSET_USER действие отправлено,record Редуктор должен также установить статус записей, относящихся к наблюдаемым таблоloading.В действии создатель, который выполняет фактический побочный эффект получения / отправки баллов.

Также кажется, что я должен вручную отслеживать всех активных наблюдателей. Тогда как в версии Bacon.js я сделал что-то вроде этого:

Bacon.once() // When first observing the scoreboard
.merge(resubmit口) // When resubmitting because of network error
.merge(user川.changes().filter(user => !!user).first()) // When user logs in (but only once)
.flatMapLatest(submitOrGetRanking(data))

Реальный код Бэкона намного длиннее, из-за всех сложных правил, приведенных выше, которые делали версию Бэкона едва читаемой.

Но Бэкон автоматически отслеживал все активные подписки. Это заставило меня задуматься о том, что это может не стоить переключения, потому что перезапись этого в Redux потребует много ручной обработки. Кто-нибудь может предложить какой-нибудь указатель?

Ответы на вопрос(2)

Ваш ответ на вопрос