typescript ngrx/effects 库的目的是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39552067/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 03:51:28  来源:igfitidea点击:

What is the purpose of ngrx/effects library?

javascriptangulartypescriptreduxngrx

提问by Stav Alfi

I have failed to find any usefull information about this library or what is the purpose of it. It seems like ngrx/effectsexplain this library to developers who already know this concept and gives a bigginer example on how to code.

我没有找到任何关于这个库的有用信息或者它的目的是什么。似乎ngrx/effects向已经知道这个概念的开发人员解释了这个库,并给出了一个关于如何编码的更大的例子。

My questions:

我的问题:

  1. What are sources of actions?
  2. What is the purpose of ngrx/effects library;what is the downside of only using ngrx/store?
  3. When it is recommended to be used?
  4. Does it support angular rc 5+? How do we configure it in rc 5+?
  1. 什么是行动来源?
  2. ngrx/effects 库的目的是什么;只使用 ngrx/store 的缺点是什么?
  3. 什么时候推荐使用?
  4. 它是否支持 angular rc 5+?我们如何在 rc 5+ 中配置它?

Thanks!

谢谢!

回答by George Zhou

The topic is too wide. It will be like a tutorial. I will give it a try anyway. In a normal case, you will have an action, reducer and a store. Actions are dispatched by the store, which is subscribed to by the reducer. Then the reducer acts on the action, and forms a new state. In examples, all states are at the frontend, but in a real app, it needs to call backend DB or MQ, etc, these calls have side effects. The framework used to factor out these effects into a common place.

话题太广了。它会像一个教程。无论如何我都会试一试。在正常情况下,您将拥有一个 action、reducer 和一个 store。Actions 由 store 分派,而 store 被 reducer 订阅。然后reducer作用于action,形成一个新的状态。在示例中,所有状态都在前端,但在实际应用中,它需要调用后端 DB 或 MQ 等,这些调用有副作用。该框架用于将这些影响分解为一个共同的地方。

Let's say you save a Person Record to your database, action: Action = {type: SAVE_PERSON, payload: person}. Normally your component won't directly call this.store.dispatch( {type: SAVE_PERSON, payload: person} )to have the reducer call the HTTP service, instead it will call this.personService.save(person).subscribe( res => this.store.dispatch({type: SAVE_PERSON_OK, payload: res.json}) ). The component logic will get more complicated when adding real life error handling. To avoid this, it will be nice to just call this.store.dispatch( {type: SAVE_PERSON, payload: person} )from your component.

假设您将个人记录保存到您的数据库中,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} )从您的组件中调用 。

That is what the effects library is for. It acts like a JEE servlet filter in front of reducer. It matches the ACTION type (filter can match urls in java world) and then acts on it, and finally returns a different action, or no action, or multiple actions. Then the reducer responds to the output actions of effects.

这就是效果库的用途。它的作用类似于减速器前面的 JEE servlet 过滤器。它匹配ACTION类型(过滤器可以匹配java世界中的url)然后对其进行操作,最后返回一个不同的动作,或者没有动作,或者多个动作。然后reducer 响应effects 的输出动作。

To continue the previous example, with the effects library:

要继续上一个示例,使用效果库:

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

The weave logic is centralised into all Effects and Reducers classes. It can easily grow more complicated, and at the same time this design makes other parts much simpler and more re-usable.

编织逻辑集中在所有 Effects 和 Reducers 类中。它很容易变得更复杂,同时这种设计使其他部件更简单,更易于重复使用。

For example if the UI has auto saving plus manually saving, to avoid unnecessary saves, UI auto save part can just be triggered by timer and manual part can be triggered by user click. Both would dispatch a SAVE_CLIENT action. The effects interceptor can be:

例如,如果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

The call

电话

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

only works once if there is an error. The stream is dead after an error is thrown because the catch tries on outer stream. The call should be

如果出现错误,则仅工作一次。抛出错误后流就死了,因为 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}) ) )

Or another way: change all ServiceClass services methods to return ServiceResponse which contains error code, error message and wrapped response object from server side, i.e.

或者另一种方式:更改所有 ServiceClass 服务方法以从服务器端返回包含错误代码、错误消息和包装响应对象的 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};
    }

    ... ...
}

One correction to my previous comment: Effect-Class and Reducer-Class, if you have both Effect-class and Reducer-class react to the same action type, Reducer-class will react first, and then Effect-class. Here is an example: One component has a button, once clicked, called: this.store.dispatch(this.clientActions.effectChain(1));which will be handled by effectChainReducer, and then ClientEffects.chainEffects$, which increases the payload from 1 to 2; wait for 500 ms to emit another action: this.clientActions.effectChain(2), after handled by effectChainReducerwith payload=2 and then ClientEffects.chainEffects$, which increases to 3 from 2, emit this.clientActions.effectChain(3), ..., until it is greater than 10, ClientEffects.chainEffects$emits this.clientActions.endEffectChain(), which changes the store state to 1000 via effectChainReducer, finally stops here.

对我之前评论的一个更正:Effect-Class 和 Reducer-Class,如果你让 Effect-class 和 Reducer-class 对相同的 action 类型做出反应,Reducer-class 会先反应,然后是 Effect-class。下面是一个例子:一个组件有一个按钮,一旦被点击,调用:this.store.dispatch(this.clientActions.effectChain(1));将由effectChainReducer,然后处理ClientEffects.chainEffects$,它将有效负载从 1 增加到 2;等待 500 ms 发出另一个动作:this.clientActions.effectChain(2), 在处理后effectChainReducer使用 payload=2 然后ClientEffects.chainEffects$,从 2 增加到 3,发出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;
      }
    }

If you run the above code, the output should look like:

如果你运行上面的代码,输出应该是这样的:

client-reducer.ts:51 reducer chain are at level: 1
client-effects.ts:72 effect chain are at level: 1
client-reducer.ts:51 reducer chain are at level: 2
client-effects.ts:72 effect chain are at level: 2
client-reducer.ts:51 reducer chain are at level: 3
client-effects.ts:72 effect chain are at level: 3
... ...
client-reducer.ts:51 reducer chain are at level: 10
client-effects.ts:72 effect chain are at level: 10

client-reducer.ts:51 减速器链在级别:1
client-effects.ts:72 效果链在级别:1
client-reducer.ts:51 减速器链在级别:2
client-effects.ts:72 effect链在级别:2
client-reducer.ts:51 减速器链在级别:3
client-effects.ts:72 效果链在级别:3
... ...
client-reducer.ts:51 减速器链是在级别:10
client-effects.ts:72 效果链在级别:10

It indicates reducer runs first before effects, Effect-Class is a post-interceptor, not pre-interceptor. See flow diagram: enter image description here

它表示reducer在效果之前先运行,Effect-Class是一个后拦截器,而不是前拦截器。见流程图: 在此处输入图片说明