[英]ngRx state update and Effects execution order
我對這個問題有自己的看法,但最好仔細檢查並確定。 感謝您的關注並嘗試提供幫助。 這里是:
想象一下,我們正在調度一個觸發一些狀態變化的動作,並且還附加了一些效果。 所以我們的代碼必須做兩件事 - 改變狀態並做一些副作用。 但這些任務的順序是什么? 我們是在同步嗎? 我相信,首先,我們改變狀態,然后做副作用,但有可能,這兩個任務之間可能會發生其他事情嗎? 像這樣:我們改變狀態,然后在我們之前做過的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
所以問題是,如果不好的情況真的發生了嗎?
所以我們的代碼必須做兩件事 - 改變狀態並做一些副作用。 但這些任務的順序是什么? 我們是在同步嗎?
假設我們發送動作A.我們有一些處理動作A的reducers。它們將按照傳遞給StoreModule.provideStore()的對象中指定的順序被調用。 然后,聽取動作A的副作用將在下一次觸發。 是的,它是同步的。
我相信,首先,我們改變狀態,然后做副作用,但有可能,這兩個任務之間可能會發生其他事情嗎? 像這樣:我們改變狀態,然后在我們之前做過的HTTP請求上得到一些響應並處理它,然后做副作用。
從去年中期開始我一直在使用ngrx,我從來沒有注意到這種情況。 我發現,每次調度一個動作時,它都會經歷由減速器首先處理的整個循環,然后在處理下一個動作之前通過副作用。
我認為這是必須的,因為redux(ngrx從中演變而來)將自己稱為主頁上可預測的狀態容器。 通過允許發生不可預測的異步操作,您將無法預測任何內容,並且redux dev工具不會非常有用。
編輯#1
所以我剛做了一個測試。 我運行了一個'LONG'動作,然后副作用將運行一個需要10秒的操作。 與此同時,我可以繼續使用UI,同時向州提供更多的調度。 最后,'LONG'的效果已完成並發送'LONG_COMPLETE'。 我認為減速器和副作用是一種交易是錯誤的。
這就是說我認為仍然很容易預測發生了什么,因為所有州的變化仍然是交易性的。 這是一件好事,因為我們不希望UI在等待長時間運行的api調用時阻塞。
編輯#2
因此,如果我理解正確,你的問題的核心是關於switchMap和副作用。 基本上你問的是,如果響應在我運行reducer代碼時返回,然后使用switchMap運行副作用以取消第一個請求。
我想出了一個我認為可以回答這個問題的測試。 我設置的測試是創建2個按鈕。 一個叫Quick,一個叫Long。 Quick將發送'QUICK',Long將發送'LONG'。 收聽Quick的reducer將立即完成。 收聽Long的reducer需要10秒鍾才能完成。
我設置了一個可以同時監聽Quick和Long的單一副作用。 這假裝通過使用'of'來模擬api調用,這讓我從頭開始創建一個observable。 然后在調度'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'
}
)
});
在測試期間,我點擊了快速按鈕,然后立即點擊了長按鈕。
這是發生了什么:
因此,switchMap確實取消了,不應該發生不良情況。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.