发出操作,然后请求并使用redux-observable和rxjs

时间:2017-06-22 18:11:13

标签: redux rxjs redux-observable

所以,我有一个接收SUBMIT_LOGIN动作的史诗,然后它应该触发generateDeviceId函数,该函数返回一个id为有效载荷的动作。在由reducer处理并且商店更新之后,它应该请求登录,然后解析它以存储并最终将用户重定向到我们的仪表板

const generateDeviceId = (deviceId)  => (({type: GENERATE_DEVICE_ID, payload: deviceId}));

const resolveLogin = (response) => ({type: RESOLVE_LOGIN, payload: response});

const submitLogin = (email, password) => ({type: SUBMIT_LOGIN, payload: {email, password}});

const requestLogin = (email, password) => ({type: REQUEST_LOGIN, payload: {email, password}});

const loadAbout = () => ({type: LOAD_ABOUT});

const submitLoginEpic = (action$) =>
        action$
            .ofType(SUBMIT_LOGIN)
            .mapTo(generateDeviceId(uuidv1()))
                .flatMap(({payload}) => login(payload.email, payload.password)
                    .flatMap(({response}) => [resolveLogin(response.content), loadAbout()])
                );

ps:login函数是来自ajax的{​​{1}},它返回一个流:

rx-dom

ps2:const AjaxRequest = (method, url, data) => { const state = store.getState(); const {token, deviceId} = state.user; return ajax({ method, timeout: 10000, body: data, responseType: 'json', url: url, headers: { token, 'device-id': deviceId, 'Content-Type': 'application/json' } }); }; const login = (email, password) => AjaxRequest('post', 'sign_in', {email, password}); 函数只生成一个随机密钥(它是一个lib)

我认为(实际上我确定)我做错了,但两天之后我真的不知道如何继续。 :/

更新

在谢尔盖第一次更新之后,我已经将我的史诗改为了,但不幸的是由于某种原因uuidv1 ajax的效果不像谢尔盖的rx-dom's可见。我们目前正在研究这个问题。

login$

更新2

在谢尔盖第二次更新后,我再次更改了我的代码,最后找到了一个解决方案,我使用const generateDeviceId = (deviceId) => (({type: GENERATE_DEVICE_ID, payload: deviceId})); const resolveLogin = (response) => ({type: RESOLVE_LOGIN, payload: response}); const submitLogin = (email, password) => ({type: SUBMIT_LOGIN, payload: {email, password}}); const requestLogin = (email, password) => ({type: REQUEST_LOGIN, payload: {email, password}}); const loadAbout = () => ({type: LOAD_ABOUT}); const submitLoginEpic = action$ => action$.ofType(SUBMIT_LOGIN) .mergeMap(({payload}) => Observable.of(generateDeviceId(uuid())) .concat(login(payload.email, payload.password) .concatMap(({response}) => [resolveLogin(response.content), loadAbout()]) two epics运算符来.concatMap调度操作并synchronously 1}}。

works as expected

1 个答案:

答案 0 :(得分:4)

如果我做对了,你希望你的史诗能够针对每个SUBMIT_LOGIN产生以下一系列行动:

GENERATE_DEVICE_ID -- RESOLVE_LOGIN -- LOAD_ABOUT

另外,我想在GENERATE_DEVICE_ID收到后需要立即发出SUBMIT_LOGIN, 只有在RESOLVE_LOGIN返回的流发出后,才会发出LOAD_ABOUTlogin()

如果我的猜测是正确的,那么你只需要启动嵌套的observable(每个SUBMIT_LOGIN创建一个) 使用GENERATE_DEVICE_ID操作和startWith操作符就是这样:

const submitLoginEpic = action$ =>
    action$.ofType(SUBMIT_LOGIN)
        .mergeMap(({ payload }) =>
            login(payload.email, payload.password)
                .mergeMap(({ response }) => Rx.Observable.of(resolveLogin(response.content), loadAbout()))
                .startWith(generateDeviceId(uuidv1()))
        );

更新:一种可能的替代方法是使用concat运算符:obs1.concat(obs2)仅在obs2完成时订阅obs1。< / p>

另请注意,如果在调度login()之后需要调用GENERATE_DEVICE_ID,您可能希望将其包装在“冷”可观察对象中:

const login$ = payload =>
    Rx.Observable.create(observer => {
        return login(payload.email, payload.password).subscribe(observer);
    });

const submitLoginEpic = action$ =>
    action$.ofType(SUBMIT_LOGIN)
        .mergeMap(({ payload }) =>
            Rx.Observable.of(generateDeviceId(uuidv1()))
                .concat(login$(payload).map(({ response }) => resolveLogin(response.content)))
                .concat(Rx.Observable.of(loadAbout()))
        );

这种方式GENERATE_DEVICE_ID在调用login()之前发出,即序列为

GENERATE_DEVICE_ID -- login() -- RESOLVE_LOGIN -- LOAD_ABOUT

更新2: login()无法正常工作的原因是因为它取决于外部状态(const state = getCurrentState()),该状态在login()时不同{调用{1}}时,login()返回的observable被订阅。 AjaxRequest在调用login()时捕获状态,这在GENERATE_DEVICE_ID被调度到商店之前发生。此时尚未执行任何网络请求,但已基于错误状态配置ajax observable。

要看看会发生什么,让我们简化一下并以这种方式重写史诗:

const createInnerObservable = submitLoginAction => {
    return Observable.of(generateDeviceId()).concat(login());
}

const submitLoginEpic = action$ =>
    action$.ofType(SUBMIT_LOGIN).mergeMap(createInnerObservable);

SUBMIT_LOGIN操作到达时,mergeMap()首先调用createInnerObservable()函数。该函数需要创建一个新的observable,为此,它必须调用generateDeviceId()login()函数。当调用login()时,状态仍然是旧的,因为此时内部observable尚未创建,因此没有机会调度GENERATE_DEVICE_ID。因为login()返回一个配置了旧数据的ajax observable,它将成为生成的内部observable的一部分。只要createInnerObservable()返回,mergeMap()就会订阅返回的内部observable并开始发出值。 GENERATE_DEVICE_ID首先出现,被分派到商店并且状态发生变化。之后,ajax observable(现在是内部observable的一部分)被订阅并执行网络请求。但新状态对此没有影响,因为ajax observable已经使用旧数据进行了初始化。

login包含到Observable.create中会延迟通话,直到订阅Observable.create返回的可观察对象为止,此时状态已经是最新状态。

另一种方法可能是引入一个额外的史诗,它会对GENERATE_DEVICE_ID动作(或不同的动作,适合您的域名)做出反应并发送登录请求,例如:

const submitLogin = payload => ({ type: "SUBMIT_LOGIN", payload });

// SUBMIT_LOGIN_REQUESTED is what used to be called SUBMIT_LOGIN
const submitLoginRequestedEpic = action$ =>
    action$.ofType(SUBMIT_LOGIN_REQUESTED)
        .mergeMap(({ payload }) => Rx.Observable.of(
            generateDeviceId(uuidv1()),
            submitLogin(payload))
        );

const submitLoginEpic = (action$, store) =>
    action$.ofType(SUBMIT_LOGIN)
        .mergeMap(({ payload }) => {
            // explicitly pass all the data required to login
            const { token, deviceId } = store.getState().user;
            return login(payload.email, payload.password, token, deviceId)
                .map(({ response }) => resolveLogin(response.content))
                .concat(loadAbout());
        });

学习资源

由于redux-observable基于RxJS,因此首先要熟悉Rx是有意义的。

我强烈建议观看AndréStaltz的"You will learn RxJS"演讲。它应该直观地了解可观察物是什么以及它们如何在引擎盖下工作。

André还在egghead上撰写了这些非凡的课程:

杰伊·菲尔普斯也在redux-observable上给了a brilliant talk,这绝对值得一看。