@ngrx/store 如何确保reducer中的state改变之后再发送请求?

发布于 2022-09-05 15:47:13 字数 4877 浏览 14 评论 0

应用场景

一个手机应用,在启动的时候会去检查用户是否在之前已经登陆过(在本地存储或者IndexedDB中检查),
如果已经登陆则使用登陆信息(accessToken)去获取数据,如果没有登陆, 则不带登陆信息获取数据。 

模块

  1. 前端框架:angular4
  2. UI框架:ionic3
  3. 状态管理:@ngrx系列全家桶(@ngrx/store 4.0、@ngrx/effects 4.0、@ngrx/db 2.0)

Action

app.action.ts

import { Action } from '@ngrx/store';
import { User } from '../models/user';

// 应用在启动的时候触发的Action名称
export const APP_INIT = '[App] Initial';
// 应用启动触发Action成功
export const APP_INIT_SUCCESS = '[App] Initial Success';

// 应用在启动的时候触发的Action模型
export class AppInitialAction implements Action{
    readonly type = APP_INIT;
    constructor() { }
}

// 应用启动触发Action成功的模型
export class AppInitialSuccessAction implements Action {
    readonly type = APP_INIT_SUCCESS;
    // 构造中的参数为User对象,由于用户可能没有登录, 可能为undefined或者null
    constructor(public payload: User) { }
}

export type Actions = AppInitialAction | AppInitialSuccessAction;

Reducer

user.reducer.ts 中只列出关键部分.

import { User } from '../models/user';
import * as app from '../actions/app.action';
// 状态模型定义
export interface State {
    isFetching: boolean; // 是否正在发送请求
    checked: boolean; // checked用于标识是否已经进行了用户登录检查
    error: boolean; // 是否发生错误
    message: string;// 错误/成功消息
    user: User; // 当前登录用户模型,其中包含accessToken
}
// 默认状态
const initialState: State = {
    isFetching: false, //未发送请求
    checked: false, // 未进行检查
    error: false,    //没有错误发生
    message: undefined, // 没有消息
    user: undefined // 用户没有登录
}

export const reducer = function (state: State = initialState, action: app.Actions): State {
    switch (action.type) {
        case app.APP_INIT_SUCCESS: // 检查用户登录完成
            // 由于用户可能在之前未进行登录,所以返回的可能是undefined或者null
            // 如果以下条件不成立,则说明用户之前已经登录过。
            if (action.payload != undefined && action.payload != null) {
                return Object.assign({}, state, {
                    checked: true,
                    user: action.payload
                });
            } else {
                // 即使用户未登录,也需要保存已经检查过是否登录,避免重复检查。
                return Object.assign({},state,{ checked:true });
            }
        default:
            return state;
    }
}

Effects

下面是判断用户登录的关键部分, 经过调试和跟踪,能够正常运行。

// 在初始化应用的action中判断用户是否登录
@Effect() init$$: Observable<Action> = this.actions$
    .ofType(app.APP_INIT)
    .switchMap(() =>
        this.db.query('user').map((u: User) => u).toArray()
            .map((users: User[]) => {
                const x = new app.AppInitialSuccessAction({
                    id: '11111',
                    loginname: 'ycpaladin',
                    avatar_url: 'https://avatars3.githubusercontent.com/u/3337028?v=4&s=120',
                    accessToken: 'xxxx-zzzz-xxxx-cccc'
                });
                return x;
            }));

// 自动触发初始化应用的请求
@Effect() init$ = defer(() => {
    return of(new app.AppInitialAction());
    // this.store.dispatch(new app.AppInitialAction());
});

Component

data: Observable<Topic[]>;
checked: Observable<boolean>;
@Input() tabName: string;
constructor(private navCtrl: NavController, private store: Store<fromRoot.State>) {
    this.data = this.store.select(fromRoot.getTopics); //  对应到数据请求的结果
    this.checked = this.store.select(fromRoot.getChecked);  // 对应user.reducer中的checked, 其默认值一定是false
}

// 
ngOnInit():void{
    // 使用zip操作符监视2个属性
    zip(this.checked, this.data)
    // 主要判断this.checked中的value是否为true
    .filter(([checked , data]) => {
        return checked === true && data.length === 0;
    })
    .subscribe(([checkedUser, data]) => {
        // 获取数据
        this.store.dispatch(new topic.LoadAction({ tabName: this.tabName, pageIndex: 1 }));
    });
    
}

问题

在Component中的ngOnInit方法中这些出现了问题, 默认他不会去获取数据。 我跟踪了一下,还是在filter方法中checked的值为false导致的。

实际上在fliter方法上打上断点等待一定的时间之后,里面的条件是成立的,结果就是数据按预期的样子出现在页面中了,但是这种行为是不正确的。

我觉着这其实是一个先后顺序的问题,所以有没有办法确保checked的值为true,之后再去请求数据?

另外,我认为redux中应该也会遇到这样的场景,应该怎样解决呢?

临时解决方案

目前我只想到一种办法,在ngOnInit中使用setInterval去轮询:

const timer = setInterval(() => {
    const x = zip(this.checkedUser, this.data)
        .filter(([checkedUser, data]) => {
            return checkedUser === true && data.length === 0;
        })
        .subscribe(([checkedUser, data]) => {
            this.store.dispatch(new topic.LoadAction({ tabName: this.tabName, pageIndex: 1 }));
            if (x !== undefined){
                x.unsubscribe();
            }
            clearInterval(timer);
                
        });
}, 0); 

还有更好的办法吗?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

ま昔日黯然 2022-09-12 15:47:13

this.data = this.store.select(fromRoot.getTopics),不是很清楚这个究竟是什么数据的结果。

但从你的问题描述‘在fliter方法上打上断点等待一定的时间之后,里面的条件是成立的’来看,我猜是zip操作符的问题,zip流弹珠模型如下:

---1---2---3---4----|--->
---a--b--c--|->
    zip
---1a--2b--3c-|->

建议用withLatestFrom

    this.checked.withLatestFrom(
        this.data, 
        (checked, data) => checked && !data.length
    )
    .filter(condition => condition)
    .subscribe(_ => this.store.dispatch(new topic.LoadAction({ tabName: this.tabName, pageIndex: 1 })));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文