Я много часов пытался найти решение этой проблемы...
Я разрабатываю игру с онлайн-табло. Игрок может войти в систему и выйти из системы в любое время. После окончания игры игрок увидит табло и увидит их собственное звание, и оценка будет отправлена автоматически.
Табло показывает рейтинг игроков и таблицу лидеров.
Табло используется как при завершении игры пользователем (для подачи оценки), так и когда пользователь просто хочет проверить свой рейтинг.
Здесь логика становится очень сложной:
-
Если пользователь вошел в систему, сначала будет отправлен счет. После сохранения новой записи табло будет загружено.
-
В противном случае табло будет загружено немедленно. Игроку будет предоставлена возможность входа в систему или регистрации. После этого счет будет отправлен, а затем табло снова будет обновлено.
-
Однако, если нет оценки для представления (просто просмотр таблицы с высокими баллами). В этом случае игроки, уже записанные, просто загружаются. Но поскольку это действие не влияет на табло, одновременно должны быть загружены как табло, так и запись игроков.
-
Существует неограниченное количество уровней. У каждого уровня есть другой табло. Когда пользователь просматривает табло, пользователь "наблюдает за табло. Когда он закрыт, пользователь перестает его наблюдать.
-
Пользователь может войти в систему и выйти из системы в любое время. Если пользователь выходит из системы, рейтинг пользователей должен исчезнуть, и если пользователь входит в систему как другая учетная запись, тогда должна быть выбрана и отображена ранжирующая информация для этой учетной записи.
... но эта выборка этой информации должна выполняться только для табло, чей пользователь в настоящее время наблюдает.
-
Для просмотра операций результаты должны быть кэшированы в памяти, поэтому, если пользователь повторно подписывается на тот же табло, выбор не будет. Однако, если есть оценка, кэш не должен использоваться.
-
Любой из этих сетевых операций может быть неудачным, и игрок должен иметь возможность повторить их.
-
Эти операции должны быть атомарными. Все состояния должны обновляться за один раз (без промежуточных состояний).
В настоящее время я могу решить эту проблему с помощью Bacon.js(функциональной библиотеки реактивного программирования), поскольку она поставляется с поддержкой Atom-обновления. Код довольно краткий, но сейчас это грязный непредсказуемый код спагетти.
Я начал смотреть на 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
.
В документах Async Actions, как они рекомендуют, следует поместить их в создателя действия. Означает ли это, что логика отправки оценки должна быть добавлена в создателя действия для успешного действия входа?
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)
}
}
}
Я нахожу это немного странным, потому что код, ответственный за эту цепную реакцию, теперь существует в двух местах:
- В редукторе. Когда отправляется действие
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))
Фактический код Бэкона намного длиннее из-за всех сложных правил выше, что сделало версию Bacon едва удобочитаемой.
Но Бэкон автоматически отслеживает все активные подписки. Это привело меня к тому, что я начал спрашивать, что это может не стоить переключения, потому что переписывание этого в Redux потребует много ручной обработки. Может кто-нибудь предложить какой-то указатель?