如何用 ngrx/effects 执行多个相关的 operations/effects/actions

How to perform multiple related operations/effects/actions with ngrx/effects

我正在开发一个使用 ngrx/store 1.5 和 thunk 中间件的应用程序,我正在尝试迁移到 ngrx/store 2.0 和 ngrx/effects。关于如何处理多个相关操作 and/or 效果,我有几个问题。

我意识到 "mindset" thunks 与 effects 是不同的,我正在努力了解这些差异。我查看了可用的示例应用程序,但没有发现任何似乎适合我正在尝试的东西,所以也许我仍然完全错误地接近它。

场景一

这是处理向服务器发出登录请求的副作用:

@Effect login$: any = this.updates$
    .whenAction(LoginActions.LOGIN)
    .map(toPayload)
    .switchMap(payload => 
        this.loginService.login(payload.user, payload.password)
            .map(result => this.actions.loginSuccess(value))
            .catch((error) => Observable.of(this.loginError(error)))
));

考虑到最初的副作用,"correct" 或 "suggested" 在成功登录后触发导航到 "home" 屏幕的方法是什么?这也可以概括为简单地触发一系列动作或操作。

我考虑过的几个选项:

(a) 另一个由登录成功触发的效果,即触发后续操作以触发导航?

@Effect navigateHome$: any = this.updates$
    .whenAction(LoginActions.LOGIN_SUCCEEDED)
    .mapTo(this.actions.navigateHome());

(b)还有一个登录成功触发的效果,就是简单的进行导航操作?

@Effect navigateHome$: any = this.updates$
    .whenAction(LoginActions.LOGIN_SUCCEEDED)
    .do(this.navigateHome())
    .filter(() => false);

(c) 将附加操作与初始登录效果发出的操作连接起来? (样本显然不太正确,但给出了思路)

@Effect login$: any = this.updates$
    .whenAction(LoginActions.LOGIN)
    .map(toPayload)
    .switchMap(password => Observable.concat(
        this.loginService.login(passcode)
            .map(result => this.actions.loginSuccess(value))
            .catch((error) => Observable.of(this.loginError(error))),
        Observable.of(this.actions.navigateHome())
    ));

(d) 其他?

场景二

考虑需要按顺序发出多个请求的情况,并且随着每个请求的开始,我们想要更新 "status" 以便可以向用户提供反馈。

类似这样的东西的 thunk 示例:

multiphaseAction() {
    return (dispatch) => {
        dispatch(this.actions.updateStatus('Executing phase 1');
        this.request1()
            .flatMap(result => {
                dispatch(this.actions.updateStatus('Executing phase 2');
                return this.request2();
            })
            .flatMap(result => {
                dispatch(this.actions.updateStatus('Executing phase 3');
                return this.request3();
            })
            ...
    }
}

同样,在使用效果方法时,"correct" 或 "suggested" 的方法是什么?

我更喜欢这个,除了添加一些 .do(this.store.dispatch(this.actions.updateStatus(...)) 不知何故之外,我不确定还能做什么...

我也在学习这些东西。

如果你在链中的第一个完成时分派给减速器怎么办?

像这样:

const myReducer = (state,action:Action) => {
switch action.type {
case : "my_first_step" : runfirststep(action.payload);
case : "my_second_step": runsecondstep(action.payload);
....

function runfirststep(data) {
...
//bunch of stuff to do,
//update something to display
store.dispatch('my_second_step');
}

等等

ngrx 示例在效果文件中执行此操作。当添加或更新时,他们会调用 UPDATE_COMPLETE 或类似的东西,这样可以完成一些通知。

我正在慢慢掌握 reducers 在一个中等复杂的应用程序中最终会有多大。

场景一

我认为选项 A 和 B 都不错,但也许选项 A 只是为了导航有点过分了!如果您将导航视为此副作用的一部分,您也可以直接在 LoginActions.LOGIN 中使用路由器。

场景二

链接起来没有错@Effects。 调度一个触发你的效果 A 的动作 (SET_1_REQUEST)。效果 A returns 一个动作 (SET_1_SUCCESS) 被你的减速器接收(现在你可以向用户显示该状态)它被效果 B 拾取。

希望这是有道理的。

导航场景的答案是你的答案

@Effect navigateHome$: any = this.updates$
    .whenAction(LoginActions.LOGIN_SUCCEEDED)
    .do(this.router.navigate('/home'))
    .ignoreElements();

说明: 您对 LOGIN_SUCCESS 做出反应,并且由于路由器没有 return 新操作,我们需要停止流的传播,我们通过过滤所有内容来做到这一点。

如果你忘记过滤,路由器return未定义,这反过来会导致reducer减少一个未定义的值,这通常会在它试图读取type 的动作

另一种解决方法是使用https://github.com/ngrx/router-store

查看有关如何将路由器存储添加到您的应用程序的文档。

同样的效果现在看起来像这样。

import { go } from '@ngrx/router-store';

@Effect navigateHome$: any = this.updates$
    .whenAction(LoginActions.LOGIN_SUCCEEDED)
    .map(() => go(['/home']));

go 动作将调度路由器减速器将拾取并触发路由更改的路由器动作。

场景一

考虑 navigateHome 是否应该改变状态。另外,这个 navigateHome 动作是否从其他地方调度来实现同样的事情。如果是这样,返回一个动作是可行的方法。因此,选项A.

在某些情况下,选项 B 可能更有意义。如果navigateHome只是改变路线,可能值得考虑。

旁注:您可以在此处使用 ignoreElements 而不是 filter(() => false)

场景二

我建议在此处将您的操作链接成多个效果,并通过为每个操作修改 reducer 中的状态来提供反馈。

例如:

@Effect() trigger$: Observable<Action> = this.updates$
    .whenAction(Actions.TRIGGER)
    .do(() => console.log("start doing something here"))
    .mapTo(Actions.PHASE1);

@Effect() phase1$: Observable<Action> = this.updates$
    .whenAction(Actions.PHASE1)
    .do(() => console.log("phase 1"))
    .mapTo(Actions.PHASE2);

@Effect() phase2$: Observable<Action> = this.updates$
    .whenAction(Actions.PHASE2)
    .do(() => console.log("phase 2"))
    .ignoreElements();

并通过修改状态进行反馈:

function reducer(state = initialState, action: Action): SomeState {
    switch (action.type) {
        case Actions.TRIGGER: {
            return Object.assign({}, state, {
                triggered: true
            });
        }
        case Actions.PHASE1: {
            return Object.assign({}, state, {
                phase1: true
            });
        }
        case Actions.PHASE2: {
            return Object.assign({}, state, {
                phase1: false,
                phase2: true
            });
        }
        // ...
    }
}