2016-09-17 40 views
24

Bu kitaplık hakkında herhangi bir yararlı bilgi bulamadık veya bunun amacı nedir. ngrx/effects gibi görünüyor Bu kitaplığı zaten bu kavramı bilen ve kod nasıl bigginer bir örnek veren geliştiriciler için açıklayın.ngrx/effects kütüphanesinin amacı nedir?

Sorularım: eylemlerin kaynakları nelerdir

  1. ?
  2. ngrx/effects kütüphanesinin amacı nedir, sadece ngrx/store kullanılarak yapılan olumsuzluk nedir?
  3. Kullanılması önerildiğinde?
  4. Açısal rc 5+'yi destekliyor mu? Bunu rc 5+ içinde nasıl yapılandırabiliriz?

Teşekkürler!

+0

Ngrx mağazasının ve efektlerinin bu yaratıcılarından https://www.youtube.com/watch?v=cyaAhXHhxgk adresindeki videoyu izledim. Ngrx efektlerinin doğru bir şekilde açıklandığı benim için ilk kez. Öyleyse lütfen kontrol edin Eğer hala kafanızı sarmak gerekiyorsa. – trungk18

cevap

67

Konu geniş. Bir öğretici gibi olacak. Bir deneyeceğim. Normal durumda, eylem, redüktör ve deponuz olacak. Eylemler, indirgeyici tarafından abone olunan mağaza tarafından gönderilir ve daha sonra redüktör harekete geçerek yeni bir durum oluşturur. Öğretici için, tüm eyaletler ön plandadır, ancak gerçek uygulamada, DB veya MQ vb. Arka uçları çağırmak zorundadır, bu çağrıların, bu etkileri ortak bir yere, yani kullanılan çerçeveye dönüştürmek için yan etkileri vardır.

Kişi Kaydı'nı veritabanına kaydetmek için action: Action = {type: SAVE_PERSON, payload: person} diyelim. Normalde bileşen, HTTP hizmetini indirgemeye izin vermek için doğrudan this.store.dispatch({type: SAVE_PERSON, payload: person})'u arayamaz, bunun yerine this.personService.save(person).subscribe(res => this.store.dispatch({type: SAVE_PERSON_OK, payload: res.json}))'u arayacaktır. Gerçek hayatta hata saptamayı düşünürseniz, bileşen mantığı daha karmaşık hale gelecektir. Bunu önlemek için, bileşenden this.store.dispatch({type: SAVE_PERSON, payload: person}) numaralı telefonu arayarak sorun olmaz.

Bu etki kitaplığı için geliyor. Redüktörün önünde JEE servlet filtresi gibi davranır. ACTION türüyle eşleşir (filtre, java dünyasındaki URL'lerle eşleşebilir) ve daha sonra bunun üzerinde hareket eder, ve sonunda farklı bir eylem, ya da eylem ya da çoklu eylemler döndürür ve ardından efektlerin çıkış eylemlerine yanıtları azaltır.

etkileri kütüphane şekilde, önceki örnekte Devam:

@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}) 

örgü mantığı tüm Etkileri ve İndirgenler sınıflara merkezileştirilir. Daha karmaşık hale gelebilir ve aynı zamanda bu tasarım diğer parçaları daha basit ve daha tekrar kullanılabilir hale getirir.

Örneğin, UI'nin otomatik kaydetme ve elle kaydetme özelliği varsa, gereksiz kaydetme işlemlerinden kaçınmak için, UI otomatik kaydetme parçası yalnızca zamanlayıcı tarafından tetiklenebilir ve kullanıcı tarafından elle bölümün tetiklenmesi, her ikisi de SAVE_CLIENT eylemini, etki engelleyicide gönderir, bu olabilir: bir hata varsa

...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})) 

sadece bir kez çalışır

@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 

çağrı. Yakalama, hata yakalandıktan sonra yayılır, çünkü yakalama dış akış üzerinde çalışır. Çağrı

...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}))) 

olmalıdır; Ya da başka bir şekilde: sunucu tarafında hata kodunu, hata mesajını ve sarılmış yanıt nesnesini içeren ServiceResponse'yi döndürmek için tüm ServiceClass hizmetleri yöntemini değiştirin;Benim önceki açıklamaya

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}; 
    } 

    ... ... 
} 

Bir düzeltme: Aynı eylem türüne tepki Etkisi-sınıf ve Redüktör-sınıfı hem varsa Etkisi-Class ve Redüktör-Class, Redüktör sınıf sonra Effect ilk tepki ve edecek -sınıf. İşte bir örnek: Bir bileşenin, bir kez tıklandığında, effectChainReducer ve ClientEffects.chainEffects$ tarafından işlenecek olan this.store.dispatch(this.clientActions.effectChain(1)); adı verilen bir düğmesi vardır; bu, yükü 1'den 2'ye çıkarır; this.clientActions.effectChain(2) sonra yük 2'den 3'e kadar artar 2 ve daha sonra ClientEffects.chainEffects$, this.clientActions.effectChain(3), ..., bu 10 daha büyük olduğu kadar, ClientEffects.chainEffects$this.clientActions.endEffectChain() yayar değişikliği yayarlar = hangi effectChainReducer tarafından ele: başka bir işlem yayan 500 ms beklemek Mağaza durumu effectChainReducer aracılığıyla 1000'e, nihayet burada durur. seviyesinde 51 redüktör zinciri:

istemci reducer.ts: çıkış gibi olmalıdır

:

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; 
     } 
    } 

çalıştırmak için yukarıdaki kodu yapmak, çıkış gibi olmalıdır 1
istemci effects.ts: seviyesinde 72 etkisi zinciri: 2
istemci effects.ts: seviyesinde 51 redüktör zincir: 1
istemci reducer.ts: 72 etki zinciri düzeyde 2
istemci reducer.ts: ... ... 3

istemci reducer.ts: seviyesinde 72 etkisi zincir: 3
istemci effects.ts: 51 redüktör zincir seviyesinde 51 azaltıcı zincir düzeyde: 10
istemci effects.ts: 72 etki zinciri düzeyde: 10

Bu azaltıcı etkileri önce ilk olarak çalışır gösterir etkisi sınıfı bir post-önleme, pre-önleme olduğunu. Akış şemasına bakınız: enter image description here

+0

Bu mükemmel bir cevap! Bir soru - "Hata atıldıktan sonra akıntı öldü" yazdınız - Bir hatadan sonra neden ölecek? Başka bir konuda, lütfen benim soruma bakın http://codereview.stackexchange.com/questions/141969/designing-redux-state-tree –

+0

Güncelleme için teşekkürler! –

+0

Harika cevap! Bir şey hala bana açık değil. Bir sayfada, kullanıcının girdiği bazı verileri kaydeden bir düğme olduğunu düşünün. Bir efektle yakalanan SOME_DATA_SAVED eylemini gönderirim (redüktörler onu dinlemez), sonra kaydetmeye çalışır. Eğer başarılı olursa, SOME_DATA_SAVED_OK gönderilir (hata durumu benzer şekilde) ve bir redüktör onu yakalar ve yeni verileri bir mağazaya kaydeder. Ancak, kullanıcı arayüzünden, kaydetme veya bir hata durumunda başarı göstermenin makul bir yolunu göremiyorum. Kaydet düğmesine tıklandıktan sonra SOME_DATA_SAVED_OK/ERROR'a başvurabilirim, ancak çok garip hissettiriyor – eddyP23