2016-09-17 2 views
24

Ich habe keine nützlichen Informationen über diese Bibliothek gefunden oder was ist der Zweck davon. Es scheint wie ngrx/effects diese Bibliothek zu Entwicklern zu erklären, die dieses Konzept bereits kennen und gibt ein größeres Beispiel für Code.Was ist der Zweck der ngrx/effects-Bibliothek?

Meine Fragen:

  1. Was Quellen Aktionen sind?
  2. Was ist der Zweck von ngrx/effects library, was ist der Nachteil von nur ngrx/store?
  3. Wann wird empfohlen, verwendet zu werden?
  4. Unterstützt es Winkel RC 5+? Wie konfigurieren wir es in RC 5+?

Vielen Dank!

+0

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

Antwort

67

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: enter image description here

+0

Dies ist eine ausgezeichnete Antwort! Eine Frage - Sie haben geschrieben "Der Stream ist tot, nachdem ein Fehler geworfen wurde" - Warum wird er nach einem Fehler tot sein? In einer anderen Sache, bitte schauen Sie sich meine Frage hier http://codereview.stackexchange.com/questions/141969/designing-redux-state-tree –

+0

Danke für das Update! –

+0

Wunderbare Antwort! Eine Sache ist mir immer noch nicht klar. Stellen Sie sich vor, ich habe eine Schaltfläche auf einer Seite, die einige Daten speichert, die der Benutzer eingegeben hat. Ich sende eine SOME_DATA_SAVED-Aktion, die von einem Effekt abgefangen wird (Minderer hören nicht darauf), und versucht dann, sie zu speichern. Wenn dies gelingt, wird SOME_DATA_SAVED_OK ausgelöst (analoger Fehlerfall) und ein Reduzierer fängt es ab und speichert neue Daten in einem Geschäft. Von der Benutzeroberfläche aus kann ich jedoch keinen vernünftigen Weg sehen, einen Erfolg beim Speichern oder einen Fehler anzuzeigen. Nachdem die Schaltfläche Speichern geklickt wurde, konnte ich mich an SOME_DATA_SAVED_OK/ERROR beteiligen, aber es fühlt sich sehr merkwürdig an. – eddyP23