Ответ 1
Тема слишком широка. Это будет похоже на учебник. Я дам ему попробовать. В нормальном случае у вас будет действие, редуктор и магазин. Действия отправляются магазином, который подписывается редуктором, а затем действует редуктор на действие, формирует новое состояние. Для учебного курса все штаты находятся во внешнем интерфейсе, но в реальном приложении ему необходимо вызвать БД или MQ и т.д., Эти вызовы имеют побочные эффекты, чтобы разделить эти эффекты на обычное место, то есть структуру, используемую для.
Скажем, чтобы сохранить запись Person в базу данных, 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};
}
... ...
}
Одна поправка к моему предыдущему комментарию: Effect-Class и Reducer-Class, если у вас есть класс Effect-Class и Reducer, реагируют на один и тот же тип действия, сначала будет реагировать класс Reducer, а затем Effect-class. Вот пример:
Один компонент имеет кнопку, после щелчка которой называется: 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;
}
}
Сделать приведенный выше код запущенным, вывод должен выглядеть так:
Результат должен выглядеть так:
client-reducer.ts: цепочка редукторов 51 находится на уровне: 1
client-effects.ts: 72 цепочки эффектов находятся на уровне: 1
client-reducer.ts: цепочка редукторов 51 находится на уровне: 2
client-effects.ts: 72 цепочки эффектов находятся на уровне: 2
client-reducer.ts: цепочка редукторов 51 находится на уровне: 3
client-effects.ts: 72 цепочки эффектов находятся на уровне: 3
......
client-reducer.ts: цепочка редукторов 51 находится на уровне: 10
client-effects.ts: 72 цепочки эффектов находятся на уровне: 10
Это указывает на то, что первые шаги перед редуктором перед эффектами, Effect-Class - это пост-перехватчик, а не предварительный перехватчик. См. Блок-схему: