Тема слишком широкая. Это будет похоже на учебник. Я дам ему попробовать. В нормальном случае у вас будет действие, редуктор и магазин. Действия отправляются магазином, который подписывается редуктором, а затем действует редуктор на действие, формирует новое состояние. Для учебного курса все штаты находятся на интерфейсе, но в реальном приложении ему необходимо вызвать БД или MQ и т. Д., Эти вызовы имеют побочные эффекты, чтобы разделить эти эффекты на обычное место, которое используется в качестве основы.
Предположим сохранить запись человека в базу данных, action: Action = {type: SAVE_PERSON, payload: person}
. Обычно компонент не будет напрямую вызывать this.store.dispatch({type: SAVE_PERSON, payload: person})
, чтобы позволить вызывающему HTTP-сервису редуктора, вместо этого он будет звонить this.personService.save(person).subscribe(res => this.store.dispatch({type: SAVE_PERSON_OK, payload: res.json}))
. Логика компонента будет усложняться, если подумать об ошибке в реальной жизни. Чтобы этого избежать, будет хорошо, если просто позвонить this.store.dispatch({type: SAVE_PERSON, payload: person})
из компонента.
Это библиотека эффектов. Он действует как фильтр сервлетов JEE перед редуктором. Он соответствует типу ACTION (фильтр может соответствовать URL-адресам в java-мире), а затем действовать на нем и, наконец, возвращать другое действие или никакое действие или несколько действий, а затем редукторные ответы на выходные действия эффектов.
Продолжить на предыдущем примере, в библиотеке эффектов образом:
@Effects() savePerson$ = this.stateUpdates$.whenAction(SAVE_PERSON)
.map<Person>(toPayload)
.switchMap(person => this.personService.save(person))
.map(res => {type: SAVE_PERSON_OK, payload: res.json})
.catch(e => {type: SAVE_PERSON_ERR, payload: err})
Логика переплетения централизовано во все классы эффектов и восстановителей. Он может легко усложняться, и в то же время этот дизайн делает другие части более простыми и более пригодными для повторного использования.
Например, если пользовательский интерфейс имеет автоматическое сохранение плюс сохранение вручную, во избежание ненужных сбережений, функция автоматического сохранения пользовательской части может запускаться только по таймеру, а ручная часть запускается щелчком пользователя, оба отправляют действие SAVE_CLIENT, в перехватчик эффектов, это может быть:
@Effects() savePerson$ = this.stateUpdates$.whenAction(SAVE_PERSON)
.debounce(300).map<Person>(toPayload)
.distinctUntilChanged(...)
.switchMap(see above)
// at least 300 milliseconds and changed to make a save, otherwise no save
вызов
...switchMap(person => this.personService.save(person))
.map(res => {type: SAVE_PERSON_OK, payload: res.json})
.catch(e => Observable.of({type: SAVE_PERSON_ERR, payload: err}))
работает только один раз, если есть ошибка. Поток затухает после того, как ошибка вызывается из-за того, что catch пытается использовать внешний поток. Вызов должен быть
...switchMap(person => this.personService.save(person).map(res => {type: SAVE_PERSON_OK, payload: res.json})
.catch(e => Observable.of({type: SAVE_PERSON_ERR, payload: err})))
; Или другим способом: изменить все сервисные сервисы ServiceClasses, чтобы вернуть ServiceResponse, который содержит код ошибки, сообщение об ошибке и завернутый объект ответа со стороны сервера, т.е.
export class ServiceResult {
error: string;
data: any;
hasError(): boolean {
return error != undefined && error != null; }
static ok(data: any): ServiceResult {
let ret = new ServiceResult();
ret.data = data;
return ret;
}
static err(info: any): ServiceResult {
let ret = new ServiceResult();
ret.error = JSON.stringify(info);
return ret;
}
}
@Injectable()
export class PersonService {
constructor(private http: Http) {}
savePerson(p: Person): Observable<ServiceResult> {
return http.post(url, JSON.stringify(p)).map(ServiceResult.ok);
.catch(ServiceResult.err);
}
}
@Injectable()
export class PersonEffects {
constructor(
private update$: StateUpdates<AppState>,
private personActions: PersonActions,
private svc: PersonService
){
}
@Effects() savePerson$ = this.stateUpdates$.whenAction(PersonActions.SAVE_PERSON)
.map<Person>(toPayload)
.switchMap(person => this.personService.save(person))
.map(res => {
if (res.hasError()) {
return personActions.saveErrAction(res.error);
} else {
return personActions.saveOkAction(res.data);
}
});
@Injectable()
export class PersonActions {
static SAVE_OK_ACTION = "Save OK";
saveOkAction(p: Person): Action {
return {type: PersonActions.SAVE_OK_ACTION,
payload: p};
}
... ...
}
Одна поправки к моему предыдущему комментарию: Эффект-класс и Reducer-классу, если у вас есть оба эффект класс и Reducer класс реагируют на тот же тип действия, Редуктор-класс будет первым реагировать, а затем эффект -класс. Вот пример: Один компонент имеет кнопку, один раз щелкнул, вызвал: this.store.dispatch(this.clientActions.effectChain(1));
, который будет обрабатываться effectChainReducer
, а затем ClientEffects.chainEffects$
, что увеличивает полезную нагрузку от 1 до 2; подождите 500 мс, чтобы испустить другое действие: this.clientActions.effectChain(2)
, после обработки effectChainReducer
с полезной нагрузкой = 2, а затем ClientEffects.chainEffects$
, который увеличивается до 3 с 2, испускает this.clientActions.effectChain(3)
, ..., пока он больше 10, ClientEffects.chainEffects$
испускает this.clientActions.endEffectChain()
, который изменяется состояние хранилища до 1000 через effectChainReducer
, наконец останавливается здесь.
export interface AppState {
... ...
chainLevel: number;
}
// In NgModule decorator
@NgModule({
imports: [...,
StoreModule.provideStore({
... ...
chainLevel: effectChainReducer
}, ...],
...
providers: [... runEffects(ClientEffects) ],
...
})
export class AppModule {}
export class ClientActions {
... ...
static EFFECT_CHAIN = "Chain Effect";
effectChain(idx: number): Action {
return {
type: ClientActions.EFFECT_CHAIN,
payload: idx
};
}
static END_EFFECT_CHAIN = "End Chain Effect";
endEffectChain(): Action {
return {
type: ClientActions.END_EFFECT_CHAIN,
};
}
static RESET_EFFECT_CHAIN = "Reset Chain Effect";
resetEffectChain(idx: number = 0): Action {
return {
type: ClientActions.RESET_EFFECT_CHAIN,
payload: idx
};
}
export class ClientEffects {
... ...
@Effect()
chainEffects$ = this.update$.whenAction(ClientActions.EFFECT_CHAIN)
.map<number>(toPayload)
.map(l => {
console.log(`effect chain are at level: ${l}`)
return l + 1;
})
.delay(500)
.map(l => {
if (l > 10) {
return this.clientActions.endEffectChain();
} else {
return this.clientActions.effectChain(l);
}
});
}
// client-reducer.ts file
export const effectChainReducer = (state: any = 0, {type, payload}) => {
switch (type) {
case ClientActions.EFFECT_CHAIN:
console.log("reducer chain are at level: " + payload);
return payload;
case ClientActions.RESET_EFFECT_CHAIN:
console.log("reset chain level to: " + payload);
return payload;
case ClientActions.END_EFFECT_CHAIN:
return 1000;
default:
return state;
}
}
Сделать приведенный выше код для запуска, вывод должен выглядеть следующим образом:
Вывод должен выглядеть следующим образом:
клиент-reducer.ts: 51 редуктора цепи находятся на уровне: 1
клиент-effects.ts: 72 эффект цепи на уровне: 1
клиент-reducer.ts: 51 редуктора цепи находятся на уровне 2:
клиент-effects.ts: 72 следственной цепи находятся на уровне: 2
клиент-reducer.ts: 51 Редуктор цепь находятся на уровне: 3
клиент-effects.ts: 72 следственной цепи находятся на уровне: 3
... ...
клиент-reducer.ts: 51 редуктора цепь находятся на уровне: 10
клиент-effects.ts: 72 эффект цепи, находятся на уровне: 10
это указывает на то редуктор работает, прежде чем эффекты, эффект-класса пост-перехватчик, предварительно не-перехватчик. См. Блок-схему:
Я смотрел видео на https://www.youtube.com/watch?v=cyaAhXHhxgk от этих создателей магазина ngrx и эффектов. Первый раз для меня, где ngrx-эффекты объясняются правильно. Итак, коллеги, пожалуйста, проверьте это. Если вам все равно нужно обернуть голову. – trungk18