2016-09-17 3 views
24

Мне не удалось найти полезную информацию об этой библиотеке или в чем ее цель. Похоже, что ngrx/effects объясняет эту библиотеку разработчикам, которые уже знают эту концепцию и дают пример bigginer о том, как закодировать.Какова цель библиотеки 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

Тема слишком широкая. Это будет похоже на учебник. Я дам ему попробовать. В нормальном случае у вас будет действие, редуктор и магазин. Действия отправляются магазином, который подписывается редуктором, а затем действует редуктор на действие, формирует новое состояние. Для учебного курса все штаты находятся на интерфейсе, но в реальном приложении ему необходимо вызвать БД или MQ и т. Д., Эти вызовы имеют побочные эффекты, чтобы разделить эти эффекты на обычное место, которое используется в качестве основы.

Предположим сохранить запись человека в базу данных, 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}; 
    } 

    ... ... 
} 

Одна поправки к моему предыдущему комментарию: Эффект-класс и Reducer-классу, если у вас есть оба эффект класс и Reducer класс реагируют на тот же тип действия, Редуктор-класс будет первым реагировать, а затем эффект -класс. Вот пример: Один компонент имеет кнопку, один раз щелкнул, вызвал: 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; 
     } 
    } 

Сделать приведенный выше код для запуска, вывод должен выглядеть следующим образом:

Вывод должен выглядеть следующим образом:

клиент-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 (случай ошибки аналогично), а редуктор ловит его и сохраняет новые данные в хранилище. Однако из пользовательского интерфейса я не вижу разумного способа показать успех при сохранении или ошибке. После нажатия кнопки сохранения я могу отнестись к SOME_DATA_SAVED_OK/ERROR, но он очень странный. – eddyP23

Смежные вопросы