12

我是 ngrx 和 Redux 风格架构的新手,我在理解应该如何链接动作/效果时遇到问题。一个例子是实现基本功能,例如在用户登录后采取行动。我在这里的核心问题是用户登录后要采取的行动将根据应用程序的当前状态而有所不同——用户可能在任何地方在应用程序中出现登录提示/页面时。

我见过的任何示例都会在用户登录后发生硬编码效果。在我的场景中,如上所述,这不太适合我并不总是希望发生相同的操作。

这是包含登录组件的主页的一些示例代码。在这种情况下,我希望用户在登录后被重定向到“/购买”。

@Component(..)
export class HomePageComponent {
    constructor(private store: Store<any>) {
    }

    public onLoginSubmitted(username, password) {
        this.store.dispatch(new loginActions.Login({username, password}));
        // once this has happened successfully navigate to /buy    
    }

}

示例效果

@Injectable()
export class LoginEffects {

    ......     

    @Effect()
    login$ = this.actions$
        .ofType(loginActions.ActionTypes.LOGIN)
        .switchMap((data) => {
            return this.loginService.login(data.payload)
                .map(() => new loginActions.LoginSuccess({loggedIn: true, isLoggingIn: false}))
                .catch((error) => {
                    return of(new loginActions.LoginError({loggedIn: false, isLoggingIn: false}));
                });
        });
    ......
}

我对如何解决这个问题有几个想法——但没有一个感觉是对的。这些包括

  • 使用登录有效负载传递数据以确定下一步要采取的操作
  • 编写许多在 LoginSuccess 时采取行动但在更高级别过滤的效果
  • 通过监听商店中的事件来编写特定于组件的效果(这可能/不好的做法吗?)
  • 从存储中读取有关当前登录状态的数据并在组件中对此进行操作。但是,此逻辑将立即运行,这可能不会产生预期的效果

谁能指出我正确的道路/提供如何解决此问题的示例?

4

1 回答 1

11

mergeMap一个效果可以使用一系列动作触发多个动作。我通常会做的是,传递我希望登录效果也调度的其他操作。

@Component(..)
export class HomePageComponent {
    constructor(private store: Store<any>) { }

    public onLoginSubmitted(username, password) {
        this.store.dispatch(new loginActions.Login({
            username, 
            password, 
            onCompleteActions: [new loginActions.Redirect({ route: '/buy' })] }));
    }
}

然后登录效果将使用mergeMap. 您仍然需要为每个操作创建一个效果,但现在您可以构建更多可重用的操作,例如重定向操作。

@Injectable()
export class LoginEffects {

    ......     

    @Effect()
    login$ = this.actions$
        .ofType(loginActions.ActionTypes.LOGIN)
        .switchMap((data) => {
            let mappedActions = [new loginActions.LoginSuccess({loggedIn: true, isLoggingIn: false})];
            if (data.payload.onCompleteActions)
                mappedActions = mappedActions.concat(data.payload.onCompleteActions);

            return this.loginService.login(data.payload)
                .mergeMap(mappedActions)
                .catch((error) => {
                    return of(new loginActions.LoginError({loggedIn: false, isLoggingIn: false}));
                });
        });

    ......
}

我喜欢这个解决方案的地方在于它可以扩展到未来,因为可以传入任何操作,并且没有 switch 或 if-else 树来决定要做什么。您还可以传入多个动作,因此无需构建“redirectAndCloseModal”动作,您可以redirect传入closeModal.

this.store.dispatch(new loginActions.Login({
    username, 
    password, 
    onCompleteActions: [new loginActions.Redirect({ route: '/buy' }), new modalActions.CloseModal()] }));

您可以在这个 github 问题上阅读更多关于从一个 @Effect() 调度多个操作的信息。

编辑:关于您的评论,这不仅仅是可能让您想要停止登录的路线更改,它可能是任何东西,例如关闭模式,并且没有通用的方法来观察它。您可以向您的商店添加一些东西,例如runLoginActions布尔值,然后在登录请求返回后从您的商店中读取该数据。

然后在您的login$效果中,您可以从商店中读取它来决定是否运行其他操作。

@Effect()
login$: Observable<Action> = this.actions$
    .ofType(auth.actionTypes.LOGIN)
    .map((action: auth.LoginAction) => action.payload)
    .switchMap((user) => {
        let successAction = new loginActions.LoginSuccess({loggedIn: true, isLoggingIn: false});
        let mappedActions = [successAction];
        if (data.payload.onCompleteAction)
            mappedActions.push(data.payload.onCompleteAction);

        return this.authService.login(user.username, user.password)
            .withLatestFrom(this.store)
            .mergeMap(([result, store]) => store.runLoginActions ?
                [mappedActions] :
                [successAction]
            )
            .catch((err) => {
                try {
                    return of(new auth.NetworkFailureAction(err[0].code));
                } catch (e) {
                    throw `Array not in the expected format of [{ code: "", message: "" }]`;
                }
            });
    });

请注意,在您的 reducer 中loginSuccess,您应该设置您的runLoginActions = true, 并且默认情况下它应该以 true 开始,只有在路由更改或组件被破坏时才会关闭。

请记住,这是一个通用解决方案,您需要自己添加操作、更新减速器等。

这更像是一个手动过程,但是由于您不想执行操作的情况非常具体,因此可能必须以这种方式完成。

于 2017-02-21T18:23:34.950 回答