Как выполнить несколько связанных операций/эффектов/действий с помощью ngrx/effects
Я работаю над приложением, использующим ngrx/store 1.5 вместе с промежуточным программным обеспечением thunk, и я пытаюсь перейти к ngrx/store 2.0 и ngrx/effects. У меня есть пара вопросов относительно того, как обращаться с несколькими связанными действиями и/или эффектами.
Я понимаю, что "образ мышления" для эффектов ударных и эффектов различен, и я пытаюсь разобраться в различиях. Я просмотрел доступные примеры приложений и не нашел ничего похожего на то, что я пытаюсь, так что, возможно, я все еще не совсем прав.
Сценарий 1
Вот побочный эффект для обработки запроса на сервер для входа:
@Effect login$: any = this.updates$
.whenAction(LoginActions.LOGIN)
.map(toPayload)
.switchMap(payload =>
this.loginService.login(payload.user, payload.password)
.map(result => this.actions.loginSuccess(value))
.catch((error) => Observable.of(this.loginError(error)))
));
Учитывая исходный побочный эффект, какой был бы "правильный" или "предложенный" способ запуска навигации по "домашнему" экрану при успешном входе в систему? Это также можно обобщить на простое выполнение последовательности действий или операций.
Несколько вариантов, которые я рассмотрел:
(a) Еще один эффект, вызванный успехом входа в систему, который запускает последующее действие для запуска навигации?
@Effect navigateHome$: any = this.updates$
.whenAction(LoginActions.LOGIN_SUCCEEDED)
.mapTo(this.actions.navigateHome());
(b) Еще один эффект, вызванный успехом входа в систему, который просто выполняет операцию навигации?
@Effect navigateHome$: any = this.updates$
.whenAction(LoginActions.LOGIN_SUCCEEDED)
.do(this.navigateHome())
.filter(() => false);
(c) Объединение дополнительных действий с теми, которые исходят из первоначального эффекта входа? (образец явно не совсем корректный, но дает идею)
@Effect login$: any = this.updates$
.whenAction(LoginActions.LOGIN)
.map(toPayload)
.switchMap(password => Observable.concat(
this.loginService.login(passcode)
.map(result => this.actions.loginSuccess(value))
.catch((error) => Observable.of(this.loginError(error))),
Observable.of(this.actions.navigateHome())
));
(d) Другое?
Сценарий 2
Рассмотрим случай, когда несколько запросов необходимо выполнить последовательно, и по мере начала каждого запроса мы хотим обновить "статус", чтобы пользователь мог получить обратную связь.
Пример трюка для чего-то в этих строках:
multiphaseAction() {
return (dispatch) => {
dispatch(this.actions.updateStatus('Executing phase 1');
this.request1()
.flatMap(result => {
dispatch(this.actions.updateStatus('Executing phase 2');
return this.request2();
})
.flatMap(result => {
dispatch(this.actions.updateStatus('Executing phase 3');
return this.request3();
})
...
}
}
Опять же, какой был бы "правильный" или "предложенный" способ сделать это при использовании подхода эффектов?
Этот я больше застрял, не совсем уверен, что можно сделать, кроме как добавить немного .do(this.store.dispatch(this.actions.updateStatus(...))
как-то...
Ответы
Ответ 1
Ответ для сценария навигации - ваш ответ b
@Effect navigateHome$: any = this.updates$
.whenAction(LoginActions.LOGIN_SUCCEEDED)
.do(this.router.navigate('/home'))
.ignoreElements();
Объяснение:
Вы реагируете на LOGIN_SUCCESS, и поскольку маршрутизатор не возвращает новое действие, нам нужно остановить распространение потока, что мы делаем, фильтруя все.
Если вы забудете фильтровать, маршрутизатор возвращает undefined, что, в свою очередь, приведет к сокращению, чтобы уменьшить значение undefined, которое обычно приводит к нулевому указателю, когда он пытается прочитать type
действие
Другим способом решения этой проблемы является использование https://github.com/ngrx/router-store
Проверьте документацию о том, как добавить в ваше приложение хранилище маршрутизатора.
Тот же эффект теперь будет выглядеть следующим образом.
import { go } from '@ngrx/router-store';
@Effect navigateHome$: any = this.updates$
.whenAction(LoginActions.LOGIN_SUCCEEDED)
.map(() => go(['/home']));
В действии go
будет отправлено действие маршрутизатора, которое редуктор маршрутизатора поднимет и вызовет изменение маршрута.
Ответ 2
Сценарий 1
Учитывайте, должно ли /t navigateHome
изменять состояние. Кроме того, независимо от того, отправлено ли это действие navigateHome
из других мест, чтобы достичь того же. Если это так, возвращение действия - это путь. Поэтому опция A.
В некоторых случаях вариант B может иметь больше смысла. Если navigateHome
изменяет маршрут только, его можно счесть.
Боковое примечание: здесь вы можете использовать ignoreElements вместо filter(() => false)
.
Сценарий 2
Я бы предложил связать ваши действия здесь в нескольких эффектах и дать обратную связь, изменив состояние в редукторе для каждого действия.
Например:
@Effect() trigger$: Observable<Action> = this.updates$
.whenAction(Actions.TRIGGER)
.do(() => console.log("start doing something here"))
.mapTo(Actions.PHASE1);
@Effect() phase1$: Observable<Action> = this.updates$
.whenAction(Actions.PHASE1)
.do(() => console.log("phase 1"))
.mapTo(Actions.PHASE2);
@Effect() phase2$: Observable<Action> = this.updates$
.whenAction(Actions.PHASE2)
.do(() => console.log("phase 2"))
.ignoreElements();
И дайте отзыв, изменив состояние:
function reducer(state = initialState, action: Action): SomeState {
switch (action.type) {
case Actions.TRIGGER: {
return Object.assign({}, state, {
triggered: true
});
}
case Actions.PHASE1: {
return Object.assign({}, state, {
phase1: true
});
}
case Actions.PHASE2: {
return Object.assign({}, state, {
phase1: false,
phase2: true
});
}
// ...
}
}
Ответ 3
Сценарий 1
опции A и B оба хороши, я думаю, но, возможно, вариант A слишком много для навигации! Вы также можете напрямую использовать маршрутизатор в своем LoginActions.LOGIN
, если вы рассматриваете навигацию как часть этого побочного эффекта.
Сценарий 2
Нет ничего плохого в цепочке @Effects
.
Отправляйте действие (SET_1_REQUEST), которое запускает эффект A. Эффект A возвращает действие (SET_1_SUCCESS), которое получает ваш редуктор (теперь вы можете отобразить этот статус для пользователя), и он получает результат с помощью эффекта B.
Надеюсь, что это имеет смысл.
Ответ 4
Я тоже изучаю этот материал.
Что делать, если вы отправили на редуктор, когда первый в цепочке был выполнен?
Что-то вроде этого:
const myReducer = (state,action:Action) => {
switch action.type {
case : "my_first_step" : runfirststep(action.payload);
case : "my_second_step": runsecondstep(action.payload);
....
function runfirststep(data) {
...
//bunch of stuff to do,
//update something to display
store.dispatch('my_second_step');
}
и др.
Пример ngrx делает это в файле эффектов. Когда вы добавляете или обновляете, они вызывают UPDATE_COMPLETE или что-то подобное, предположительно, поэтому может быть сделано некоторое уведомление.
Я медленно понимаю, насколько большие редукторы окажутся в умеренно сложном приложении.