[英]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.