发出操作,然后使用redux observable和rxjs请求并发出另一个操作
因此,我有一个epic,它接收一个SUBMIT_登录操作,然后它应该启动generateDeviceId函数,该函数返回一个id为有效负载的操作。在reducer处理并更新存储之后,它应该请求登录,然后将其解析为存储,最后将用户重定向到我们的仪表板发出操作,然后使用redux observable和rxjs请求并发出另一个操作,redux,rxjs,redux-observable,Redux,Rxjs,Redux Observable,因此,我有一个epic,它接收一个SUBMIT_登录操作,然后它应该启动generateDeviceId函数,该函数返回一个id为有效负载的操作。在reducer处理并更新存储之后,它应该请求登录,然后将其解析为存储,最后将用户重定向到我们的仪表板 const generateDeviceId = (deviceId) => (({type: GENERATE_DEVICE_ID, payload: deviceId})); const resolveLogin = (response
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
函数是来自rx-dom
的ajax
函数,它返回一个流:
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});
ps2:uuidv1
函数只生成一个随机键(它是一个库)
我认为(事实上我肯定)我做错了,但两天后我真的不知道如何继续/
更新
在Sergey的第一次更新之后,我将epic改为epic,但不幸的是,由于某种原因,RXDOM的
ajax不能像Sergey的login$observable那样工作。我们目前正在做这个
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()])
更新2
在Sergey的第二次更新之后,我再次更改了代码,最终得到了一个解决方案,我使用了两个epics
和.concatMap
操作符,以便同步地调度操作,并且它按预期工作
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)
.concatMap(({payload}) => [
generateDeviceId(uuid()),
requestLogin(payload.email, payload.password)
]);
const requestLoginEpic = (action$) =>
action$
.ofType(REQUEST_LOGIN)
.mergeMap(({payload}) => login(payload.email, payload.password)
.concatMap(({response}) => [resolveLogin(response.content), loadAbout()])
如果我没弄错,您希望您的epic生成以下操作序列,以响应每个SUBMIT\u登录
:
GENERATE_DEVICE_ID -- RESOLVE_LOGIN -- LOAD_ABOUT
另外,我猜在收到SUBMIT\u LOGIN
后需要立即发布GENERATE\u DEVICE\u ID
,
而RESOLVE\u LOGIN
和LOAD\u ABOUT
只能在LOGIN()返回的流发出后发出
如果我的猜测是正确的,那么您只需要启动嵌套的observable(每个SUBMIT\u登录创建的一个)
使用GENERATE\u DEVICE\u 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)
仅在obs1
完成时订阅obs2
另外请注意,如果在调度生成设备\u ID
后需要调用login()
,您可能希望将其包装在一个“冷”的可观察对象中:
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()))
);
这样,在调用login()
之前会发出GENERATE\u DEVICE\u ID
,也就是说,序列是
GENERATE_DEVICE_ID -- login() -- RESOLVE_LOGIN -- LOAD_ABOUT
更新2:之所以login()
不能按预期工作,是因为它取决于外部状态(const state=getCurrentState()
),在调用login()
和订阅login()
返回的可观察状态的时间点不同AjaxRequest
捕获调用login()
时的状态,该状态发生在GENERATE\u DEVICE\u ID
发送到存储之前。此时还没有执行任何网络请求,但是已经基于错误状态配置了ajax
observable
为了看看会发生什么,让我们将事情简化一点,并以这种方式重写史诗:
const createInnerObservable = submitLoginAction => {
return Observable.of(generateDeviceId()).concat(login());
}
const submitLoginEpic = action$ =>
action$.ofType(SUBMIT_LOGIN).mergeMap(createInnerObservable);
当SUBMIT\u LOGIN
操作到达时,mergeMap()
首先调用createInnerObservable()
函数。函数需要创建一个新的可观察对象,为此必须调用generateDeviceId()
和login()
函数。调用login()
时,状态仍然旧,因为此时尚未创建内部可观察对象,因此没有机会调度GENERATE\u DEVICE\u ID
。因此,login()
返回一个使用旧数据配置的ajax
可观察对象,它成为结果内部可观察对象的一部分。只要createInnerObservable()
返回,mergeMap()
订阅返回的内部observable并开始发出值GENERATE\u DEVICE\u ID
首先出现,被分派到存储,状态被更改。之后,ajax
observable(现在是内部observable的一部分)被订阅并执行网络请求。但是新的状态对这一点没有影响,因为ajax
observable已经用旧数据初始化了
将login
包装到可观察对象中。create
将推迟调用,直到订阅了observatable.create
返回的可观察对象,此时状态已经是最新的
另一种方法是引入额外的epic,该epic将对生成设备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 observatable
基于RxJS,因此首先熟悉Rx是有意义的
我强烈推荐观看安德烈·斯泰尔茨的演讲。它应该给出什么是可观测的以及它们如何在引擎盖下工作的直觉
安德烈还写了这些关于书呆子的精彩课程:
此外,杰伊·菲尔普斯在《可观察的redux》
上也做了介绍,这绝对值得一看。请阅读。嘿,谢尔盖,谢谢你的回答!我已经尝试了.startWith,但问题是在登录请求之前应该发出GENERATE\u DEVICE\u ID
。所以答案是:GENERATE\u DEVICE\u ID--登录请求--解析\u Login--加载\u ABOUT
Login(payload.email,payload.password)<--这是登录请求Hey Lucas!您的意思是login()
依赖于或需要生成的设备id才能在存储中?是否应该将LOGIN\u请求
操作也发送到商店?您还可以澄清一下:login()
是在调用时立即发送请求,还是仅在订阅了它返回的可观察对象时才发送请求?我用一个使用concat
操作符的解决方案更新了答案,希望这个