2017-06-30 17 views
7

私はこの質問について私自身の意見を持っていますが、確認して確認することをお勧めします。注意を払い、手伝ってくれてありがとう。ここにあります:ngRx状態の更新とエフェクトの実行順序

いくつかの状態の変更をトリガーするアクションと、いくつかのエフェクトが添付されているとします。だから私たちのコードは2つのことを行う必要があります - 状態を変えて、いくつかの副作用をします。しかし、これらのタスクの順序は何ですか?私たちはそれを同期してやっていますか?私は最初に状態を変えて副作用をすると信じていますが、この2つのタスクの間に何か他のことが起こる可能性はありますか?このように、状態を変更してから、以前に行ったHTTPリクエストに対して何らかの応答を得て、それを処理してから、副作用を行います。

[編集:]私はここにいくつかのコードを追加することにしました。また、私はそれをたくさん簡略化しました。

州:

export interface ApplicationState { 
    loadingItemId: string; 
    items: {[itemId: string]: ItemModel} 
} 

アクション:

export class FetchItemAction implements Action { 
    readonly type = 'FETCH_ITEM'; 
    constructor(public payload: string) {} 
} 

export class FetchItemSuccessAction implements Action { 
    readonly type = 'FETCH_ITEM_SUCCESS'; 
    constructor(public payload: ItemModel) {} 
} 

リデューサー:

export function reducer(state: ApplicationState, action: any) { 
    const newState = _.cloneDeep(state); 
    switch(action.type) { 
     case 'FETCH_ITEM': 
      newState.loadingItemId = action.payload; 
      return newState; 
     case 'FETCH_ITEM_SUCCESS': 
      newState.items[newState.loadingItemId] = action.payload; 
      newState.loadingItemId = null; 
      return newState; 
     default: 
      return state; 
    } 
} 

効果:

@Effect() 
    FetchItemAction$: Observable<Action> = this.actions$ 
    .ofType('FETCH_ITEM') 
    .switchMap((action: FetchItemAction) => this.httpService.fetchItem(action.payload)) 
    .map((item: ItemModel) => new FetchItemSuccessAction(item)); 

そして、これは我々がFetchItemAction発送方法です:

export class ItemComponent { 
    item$: Observable<ItemModel>; 
    itemId$: Observable<string>; 

    constructor(private route: ActivatedRoute, 
       private store: Store<ApplicationState>) { 

     this.itemId$ = this.route.params.map(params => params.itemId); 

     itemId$.subscribe(itemId => this.store.dispatch(new FetchItemAction(itemId))); 

     this.item$ = this.store.select(state => state.items) 
      .combineLatest(itemId$) 
      .map(([items, itemId]: [{[itemId: string]: ItemModel}]) => items[itemId]) 
    } 
} 

理想のシナリオ:

User clicks on itemUrl_1; 
we store itemId_1 as loadingItemId; 
make the request_1; 
user clicks on itemUrl_2; 
we store itemId_2 as loadingItemId; 
switchMap operator in our effect cancells previous request_1 and makes request_2; 
get the item_2 in response; 
store it under key itemId_2 and make loadingItemId = null. 

悪いシナリオ:

User clicks on itemUrl_1; 
we store itemId_1 as loadingItemId; 
make the request_1; 
user clicks on itemUrl_2; 
we store itemId_2 as loadingItemId; 
we receive the response_1 before we made the new request_2 but after loadingItemId changed; 
we store the item_1 from the response_1 under the key itemId_2; 
make loadingItemId = null; 
only here our effect works and we make request_2; 
get item_2 in the response_2; 
try to store it under key null and get an error 

悪いシナリオが実際に起こるかできるのであれば問題は単純であるがない?

答えて

6

だから、我々のコードは、2つのことを行う必要があります - 状態を変更し、いくつかの側面に 効果を行います。しかし、これらのタスクの順序は何ですか? を同期させていますか?

アクションAをディスパッチします。アクションAを処理するリデューサーがいくつかあります。これらは、StoreModule.provideStore()に渡されたオブジェクトで指定された順序で呼び出されます。その後、アクションAを待ち受ける副作用が次に発生します。はい、同期しています。

これら二つのタスクの間で他の 何かが起こるかもしれないと、私は最初、私たちは状態を変更して、副作用を行うことを信じていますが、 は、可能性はありますか?このように、状態を変更してから のHTTP応答を取得して処理してから、処理します。 エフェクト。

私は昨年中頃からngrxを使用していましたが、これは決してこのようには見られませんでした。私が見つけたのは、アクションがディスパッチされるたびに、リデューサーによって最初に処理され、次に、次のアクションが処理される前に副作用が処理されるというサイクル全体を経ることです。

redux(ngrxが発展した)は、メインページの予測可能な状態コンテナとして請求されるため、これが当てはまると思います。予期しない非同期アクションが発生するのを許すことで、何かを予測することができなくなり、redux開発ツールはあまり役に立たないでしょう。

編#1

だから、僕はテストをしました。私は 'LONG'アクションを実行し、その後、副作用は10秒かかる操作を実行します。その間、私はUIを使い続けながら、州へのディスパッチを増やすことができました。最後に「LONG」の効果が終了し、「LONG_COMPLETE」が送出されました。私はレデューサーと副作用がトランザクションであることについて間違っていました。私はすべての状態の変化は、まだ取引されているので、それは何が起こっているのかを予測するのは簡単だと思うと述べた

enter image description here

。これは良いことです。なぜなら、長時間実行されているAPI呼び出しを待っている間にUIをブロックしたくないからです。

編#2

私はこれを正しく理解していればだからあなたの質問のコアがswitchMapと副作用についてです。基本的には、私が最初の要求をキャンセルするswitchMapで副作用を実行する減速コードを実行しているときに応答が戻ったらどうなるか尋ねています。

私はこの質問に答えると信じているテストを思いついた。テストのセットアップは、2つのボタンを作成することでした。一つはクイックと呼ばれ、もう一つはロングと呼ばれるクイックは「QUICK」を送り、ロングは「LONG」をディスパッチします。 Quickを聞くレデューサーはすぐに完了します。ロングを聞くレデューサーは、完了するのに10秒かかります。

私は、クイックとロングの両方を聴く単一の副作用をセットアップしました。これは 'of'を使ってapi呼び出しをエミュレートするふりをしています。最初から観測可能なものを作成しましょう。これは、 'QUICK_LONG_COMPLETE'をディスパッチする前に5秒間(.delayを使用して)待機します。

@Effect() 
    long$: Observable<Action> = this.actions$ 
    .ofType('QUICK', 'LONG') 
    .map(toPayload) 
    .switchMap(() => { 
     return of('').delay(5000).mapTo(
     { 
      type: 'QUICK_LONG_COMPLETE' 
     } 
    ) 
    }); 

私のテストでは、クイックボタンをクリックしてすぐに長いボタンをクリックしました。ここで

は何が起こったかである:

  • クイックボタンは、 'QUICK'
  • をクリック
  • 副作用が5秒で完了します、観察を開始送出されます。
  • ロングボタン 'LONG'
  • をクリックLONGを扱うリデューサーは10秒かかり
  • 送出されます。 5秒のマークで、副作用から観察可能なオリジナルは完了しますが、 'QUICK_LONG_COMPLETE'はディスパッチしません。もう5秒が経過します。
  • 「LONG」を聞く副作用は、私の最初の副作用をキャンセルするスイッチマップです。
  • 5秒が経過し、 'QUICK_LONG_COMPLETE'が送出されます。

enter image description here

したがってswitchMapはキャンセルし、あなたの悪い場合には、これまでに発生しません。

+0

返信いただきありがとうございます!はい、私が言及した最後のシナリオは非常に望ましくないです –

+0

しかし、あなたはどのようにその効果をもたらしましたか? setTimeout()を追加しましたか? 私の使用シナリオは次のとおりです: 1.ユーザーがアイテムをクリック 2.このアイテムのIDであるペイロードを持つアクションAをディスパッチします 3.一部のレデューサーは、このアイテムIDをロード状態(state.loading。 id) 4.このid(私たちはアクションをサブスクライブし、switchMapを実行します)でhttpリクエストを行います。 5.応答とディスパッチアクションA_complete を受け取ります。6.受け取ったアイテムを次のような状態に保存します。これは:state.items [state.loading.id] =レスポンスと同様にstate.loading.id = nullを実行します。 –

+0

私が見る唯一の問題は、state.loading.idを変更する可能性がありますが、別の要求をしてください。この場合私たちの状態で最後のIDは私たちの前の項目になります –

関連する問題