web-dev-qa-db-ja.com

ngrx / effectsライブラリの目的は何ですか?

このライブラリに関する有用な情報、またはその目的を見つけることができませんでした。 ngrx/effects は、この概念を既に知っている開発者にこのライブラリを説明し、コーディングの方法についての大きな例を示しているようです。

私の質問:

  1. アクションのソースは何ですか?
  2. Ngrx/effectsライブラリの目的は何ですか?ngrx/storeのみを使用することの欠点は何ですか?
  3. 使用が推奨されるのはいつですか?
  4. angular rc 5+?rc 5+でどのように設定しますか?

ありがとう!

32
Stav Alfi

トピックが広すぎます。チュートリアルのようになります。とにかく試してみます。通常の場合、アクション、レデューサー、およびストアがあります。アクションはストアによってディスパッチされ、リデューサーによってサブスクライブされます。次に、レデューサーはアクションに基づいて動作し、新しい状態を形成します。例では、すべての状態はフロントエンドにありますが、実際のアプリでは、バックエンドDBまたはMQなどを呼び出す必要があり、これらの呼び出しには副作用があります。これらの効果を共通の場所に分解するために使用されるフレームワーク。

個人レコードをデータベースaction: Action = {type: SAVE_PERSON, payload: person}に保存するとします。通常、コンポーネントはリデューサーにHTTPサービスを呼び出すためにthis.store.dispatch( {type: SAVE_PERSON, payload: person} )を直接呼び出さず、代わりに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タイプに一致します(フィルターはJava world)の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} )

織りロジックは、すべてのEffectsおよびReducersクラスに集中化されています。それは簡単により複雑になり、同時にこの設計は他の部分をよりシンプルで再利用しやすくします。

たとえば、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}) )

エラーが発生した場合にのみ機能します。キャッチは外部ストリームで試行するため、エラーがスローされた後、ストリームは停止しています。呼び出しは

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

または、すべての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};
    }

    ... ...
}

私の以前のコメントに対する1つの修正:Effect-ClassとReducer-Class、Effect-classとReducer-classの両方が同じアクションタイプに反応する場合、Reducer-classが最初に反応し、次にEffect-classが反応します。次に例を示します。1つのコンポーネントには、クリックするとボタンがあります。this.store.dispatch(this.clientActions.effectChain(1));effectChainReducerで処理され、ClientEffects.chainEffects$はペイロードを1から2に増やします; 500ミリ秒待って、別のアクションを発行します。this.clientActions.effectChain(2)、ペイロード[2]でeffectChainReducerによって処理され、次に2から3に増加するClientEffects.chainEffects$によって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;
      }
    }

上記のコードを実行すると、出力は次のようになります。

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はプリインターセプターではなくポストインターセプターです。フロー図を参照してください: enter image description here

92
George Zhou