所以,我有一个接收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$
在谢尔盖第二次更新后,我再次更改了我的代码,最后找到了一个解决方案,我使用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
答案 0 :(得分:4)
如果我做对了,你希望你的史诗能够针对每个SUBMIT_LOGIN
产生以下一系列行动:
GENERATE_DEVICE_ID -- RESOLVE_LOGIN -- LOAD_ABOUT
另外,我想在GENERATE_DEVICE_ID
收到后需要立即发出SUBMIT_LOGIN
,
只有在RESOLVE_LOGIN
返回的流发出后,才会发出LOAD_ABOUT
和login()
。
如果我的猜测是正确的,那么你只需要启动嵌套的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,这绝对值得一看。