2016-09-17 22 views
24

我沒有找到任何關於這個庫的有用信息,或者它的目的是什麼。 看起來像ngrx/effects向已經知道這個概念的開發者解釋這個庫,並給出了一個關於如何編碼的重要示例。ngrx/effects庫的目的是什麼?

我的問題:

  1. 什麼是行動的來源?
  2. ngrx/effects庫的目的是什麼;僅使用ngrx/store有什麼缺點?
  3. 什麼時候推薦使用?
  4. 它支持角度rc 5+嗎?我們如何在rc 5+中配置它?

謝謝!

+0

我從https://www.youtube.com/watch?v=cyaAhXHhxgk觀看了這些來自這些ngrx存儲和效果創作者的視頻。對我來說,第一次正確解釋ngrx效應。所以如果你仍然需要包裹頭部,請檢查它。 – trungk18

回答

67

主題太寬。它將像一個教程。我會試一試。在正常情況下,你會有行動,減速器和商店。行爲由存儲器分發,由reducer訂閱,然後reducer對行爲進行操作,形成一個新狀態。對於教程,所有狀態都在前端,但在真實應用程序中,它需要調用後端數據庫或MQ等,這些調用具有副作用,以將這些效果分解到一個共同的地方,即用於的框架。

假設要將人員記錄保存到數據庫action: Action = {type: SAVE_PERSON, payload: person}。通常組件不會直接調用this.store.dispatch({type: SAVE_PERSON, payload: person})讓Reducer調用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 servlet過濾器一樣。它匹配ACTION類型(過濾器可以匹配java世界中的URL),然後對其執行操作,最後返回一個不同的動作,或者不執行動作或多個動作,然後減少對效果輸出動作的響應。

繼續以往的例子,在特效庫方式:

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

的編織邏輯集中到所有影響和減縮類。它可以很容易地變得更加複雜,同時這種設計使得其他部件更加簡單並且更加可重用。

例如,如果UI具有自動保存和手動保存,爲了避免不必要的保存,UI自動保存部分可以由定時器觸發,手動部分由用戶點擊觸發,兩者都在效果攔截器中調度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}; 
    } 

    ... ... 
} 

一個修正我之前的評論:影響-Class和減速級,如果你有兩個作用級和減速級的反應相同的動作類型,減速級將首先反應,然後再影響-類。這裏是一個例子: 一個組件有一個按鈕,一旦點擊,調用:this.store.dispatch(this.clientActions.effectChain(1));將由effectChainReducer處理,然後ClientEffects.chainEffects$,這會將有效負載從1增加到2;等待500毫秒發射另一個動作:this.clientActions.effectChain(2),通過與effectChainReducer有效載荷= 2,然後ClientEffects.chainEffects$,它從2增加到3,發射this.clientActions.effectChain(3),...,直到其大於10,ClientEffects.chainEffects$發射this.clientActions.endEffectChain(),這改變後處理通過effectChainReducer將商店狀態改爲1000,最後停在這裏。

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

它表示減速器第一運行效果之前,影響級是一個攔截後,沒有預先攔截。請參閱流程圖: enter image description here

+0

這是一個很好的答案!一個問題 - 你寫了「錯誤被拋出後流失了」 - 錯誤之後爲什麼會死掉?在另一個問題,請看看我的問題在這裏http://codereview.stackexchange.com/questions/141969/designing-redux-state-tree –

+0

感謝您的更新! –

+0

精彩回答!有一件事對我仍然不清楚。所以想象一下,在頁面上有一個按鈕可以保存用戶輸入的一些數據。我調度SOME_DATA_SAVED被一個效果捕獲的動作(減速器不聽它),然後它試圖保存它。如果成功,則調度SOME_DATA_SAVED_OK(類似於錯誤情況),並且減速器捕獲它並將新數據保存到商店。但是,從UI中看不到保存或出錯時顯示成功的合理方法。單擊保存按鈕後,我可以轉到SOME_DATA_SAVED_OK/ERROR,但感覺非常奇怪 – eddyP23