Подтвердить что ты не робот

Какова цель библиотеки ngrx/effects?

Мне не удалось найти полезную информацию об этой библиотеке или в чем ее цель. Кажется, что ngrx/effects объясняет эту библиотеку разработчикам, которые уже знают эту концепцию и дают пример bigginer о том, как закодировать.

Мои вопросы:

  • Каковы источники действий?
  • Какова цель библиотеки ngrx/effects, что является недостатком использования только ngrx/store?
  • Когда рекомендуется использовать?
  • Поддерживает ли он angular rc 5+? Как его настроить в rc 5 +?

Спасибо!

4b9b3361

Ответ 1

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

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

    ... ...
}

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

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

Результат должен выглядеть так:

client-reducer.ts: цепочка редукторов 51 находится на уровне: 1
client-effects.ts: 72 цепочки эффектов находятся на уровне: 1
client-reducer.ts: цепочка редукторов 51 находится на уровне: 2
client-effects.ts: 72 цепочки эффектов находятся на уровне: 2
client-reducer.ts: цепочка редукторов 51 находится на уровне: 3
client-effects.ts: 72 цепочки эффектов находятся на уровне: 3
......
client-reducer.ts: цепочка редукторов 51 находится на уровне: 10
client-effects.ts: 72 цепочки эффектов находятся на уровне: 10

Это указывает на то, что первые шаги перед редуктором перед эффектами, Effect-Class - это пост-перехватчик, а не предварительный перехватчик. См. Блок-схему: введите описание изображения здесь