Thema ist zu breit. Es wird wie ein Tutorial sein. Ich werde es versuchen. Im Normalfall haben Sie Aktion, Reducer und Store. Die Aktionen werden nach Filiale gesendet, die vom Reduzierer abonniert wird, und dann wirkt Reducer auf die Aktion ein und bildet einen neuen Status. Für das Tutorial sind alle Zustände am Frontend, aber in der realen App muss es Backend DB oder MQ aufrufen, usw., diese Aufrufe haben Nebenwirkungen, um diese Effekte in einen gemeinsamen Ort auszumachen, das ist das verwendete Framework.
Nehmen wir an, Sie speichern einen Personendatensatz in der Datenbank action: Action = {type: SAVE_PERSON, payload: person}
. Normalerweise ruft die Komponente this.store.dispatch({type: SAVE_PERSON, payload: person})
nicht direkt auf, um den Reducer HTTP-Dienst aufrufen zu lassen, stattdessen ruft sie this.personService.save(person).subscribe(res => this.store.dispatch({type: SAVE_PERSON_OK, payload: res.json}))
auf. Die Komponentenlogik wird komplizierter, wenn man über Fehler im realen Leben nachdenkt. Um dies zu vermeiden, wird es nett sein, nur this.store.dispatch({type: SAVE_PERSON, payload: person})
von Komponente aufrufen.
Das ist die Effekte-Bibliothek kommt für. Es verhält sich wie JEE-Servlet-Filter vor dem Reduzierstück. Es entspricht dem ACTION-Typ (der Filter kann URLs in der Java-Welt zuordnen) und wirkt dann darauf und gibt schließlich eine andere Aktion oder keine Aktion oder mehrere Aktionen und dann Reduzierungsantworten auf die Ausgabeaktionen von Effekten zurück.
Weiter auf vorherigen Beispiel in Effektbibliothek Art und Weise:
@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})
Die Webart Logik wird zentral in alle Effekte und Reduzierungen Klassen. Es kann leicht komplizierter werden, und gleichzeitig macht dieses Design andere Teile viel einfacher und wiederverwendbarer.
Zum Beispiel, wenn UI eine automatische Speicherung plus manuelles Speichern hat, um unnötige Speicherungen zu vermeiden, kann das automatische Speichern der Benutzeroberfläche nur durch den Timer ausgelöst werden und manuell durch den Benutzerklick ausgelöst werden, beide SAVE_CLIENT-Aktionen im Effekt-Interceptor. es kann sein:
@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
Der Aufruf
...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}))
nur einmal funktioniert, wenn ein Fehler auftritt. Der Stream ist tot, nachdem ein Fehler ausgelöst wurde, weil der Catch einen externen Stream versucht. Der Anruf sollte
sein
...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})))
; Oder eine andere Möglichkeit: Ändern aller ServiceClasses-Services-Methode, um ServiceResponse zurückzugeben, die Fehlercode, Fehlermeldung und Wrapped-Response-Objekt von Serverseite enthält, d.h.
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};
}
... ...
}
Eine Korrektur zu meinem vorherigen Kommentar: Effekt-Klasse und Reducer-Klasse, wenn Sie sowohl Effect-Klasse und Reducer-Klasse reagieren auf den gleichen Aktionstyp wird Reducer-Klasse reagiert zuerst, und dann Effect -Klasse. Hier ist ein Beispiel: Eine Komponente hat eine Schaltfläche, einmal geklickt: this.store.dispatch(this.clientActions.effectChain(1));
, die von effectChainReducer
gehandhabt wird, und dann ClientEffects.chainEffects$
, die die Nutzlast von 1 bis 2 erhöht; warten auf 500 ms, um eine weitere Aktion zu senden: this.clientActions.effectChain(2)
, nach der Behandlung durch effectChainReducer
mit Nutzlast = 2 und dann ClientEffects.chainEffects$
, die von 2 auf 3 erhöht, emittieren this.clientActions.effectChain(3)
, ..., bis es größer ist als 10, emittiert this.clientActions.endEffectChain()
, die sich ändert der store state auf 1000 über effectChainReducer
, hört schließlich hier auf.
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;
}
}
den obigen Code Machen Sie ausführen können, sollte die Ausgabe wie folgt aussehen:
Client-reducer.ts: 51 Minderer Kette sind auf Stufe:
Die Ausgabe sollte wie folgt aussehen 1
Client-effects.ts: 72 Wirkungs-Kette ist in Level: 1
Client-reducer.ts: 51 Minderer Kette auf Stufe ist: 2
client-effects.ts: 72 Wirkungs-Kette auf dem Niveau ist: 2
Client-reducer.ts: 51 Minderer Kette sind auf Stufe: 3
Client-effects.ts: auf Stufe 72 Wirkungs-Kette sind: 3
... ...
client-reducer.ts: 51 reducer Kette auf der Ebene ist: 10
Client-effects.ts: 72 Wirkungs-Kette ist auf Stufe: 10
Es Minderer zeigt läuft, bevor Effekte, Effekt-Klasse ist ein post-Abfangjäger, nicht vor-Abfangjäger. Siehe Ablaufdiagramm:
Ich habe das Video unter https://www.youtube.com/watch?v=cyaAhXHhxgk von diesen Entwicklern von ngrx store und effects angeschaut. Das erste Mal für mich, wo ngrx Effekte richtig erklärt werden. Also, bitte, schau es dir an, wenn du noch deinen Kopf einwickeln musst. – trungk18