在 redux-observable 中 - 如何在不返回该操作的值的情况下分派操作 "mid-stream"?
In redux-observable - How can I dispatch an action "mid-stream" without returning the value of that action?
我到处搜索都找不到以下问题的答案...
在我的 addItemEpic 中,我想在发送 ajax 请求之前分派一个 "loading" 操作 - 我不希望操作的值被 returned - 我只是想要执行操作然后 return ajax 响应。
这可以通过 store.dispatch() 轻松实现,如下所示:
export const addItemsEpic = (action$, store) =>
action$.ofType(ADD_ITEM)
.do(() => store.dispatch({
type: 'layout/UPDATE_LAYOUT',
payload: { loading: true }
}))
.switchMap(action => api.addItem(action.payload))
.map(item => loadItem(item))
.do(() => store.dispatch({
type: 'layout/UPDATE_LAYOUT',
payload: { loading: false }
}))
.catch(error => Observable.of({
type: 'AJAX_ERROR',
payload: error,
error: true
}));
然而 store.dispatch() 在 redux-observable 中被弃用和劝阻。
我已经尝试使用几乎所有可能的运算符,但我仍然无法在不破坏下一个链接函数中的 return 值的情况下发送操作。我认为像下面这样的东西应该可以做到:
action$.ofType(ADD_ITEM)
.concatMap(action => Observable.of(
updateLayout({loading: true}),
api.addItem(action.payload)).last())
.map(item => loadItem(item))
但不幸的是这里有 2 个问题:
- 没有调度加载操作(值 returned 作为可观察值)
- api 现在请求 return 包装的承诺而不是可映射的值
我真的很感谢这里的一些帮助或建议,因为我找不到任何方法来让它工作。
提前致谢
第二种形式走在正确的道路上。这个想法是 return 一个 Observable
首先发出加载动作,然后在服务调用之后发出 loaded
动作或 error
动作,最后发出完成加载动作。
const addItemEpic = action$ => action$
.ofType(ADD_ITEM)
.switchMap(action => { // or use mergeMap/concatMap depending on your needs
const apiCall$ = Observable.fromPromise(api.addItem(action.payload))
.map(item => actions.loadItem(item))
.catch(err => Observable.of(actions.ajaxError(err)));
return Observable.concat(
Observable.of(actions.updateLayout(true)),
Observable.concat(apiCall$,
Observable.of(actions.updateLayout(false))));
});
Observable.fromPromise()
将 Promise
转换为 Observable
罗伯特的回答很好而且很正确。以下是我的一些额外想法:
重用现有操作
很多时候,当您发现自己同步执行一个又一个操作时,这表明您的减速器应该只需要第一个。
例如而不是让 layout/UPDATE_LAYOUT
设置 loading: true
,你的 reducer 可以知道只要有 ADD_ITEM
和 LOAD_ITEM
就意味着它应该是 loading: true
。如果这种隐含性不是您的包,您可以使用额外的元数据作为信号而不是操作类型本身。
const layoutReducer = (state = { loading: false }, action) => {
// instead of listening for an action type, we're looking for some other
// metadata by convention. In this case the `layout` property.
if (action.layout) {
return { ...state, ...action.layout };
} else {
return state;
}
};
// the schema you use (payload vs layout vs metadata, etc) is your call
store.dispatch({
type: 'DOESNT_MATTER',
payload: 'whatever',
layout: { loading: true }
});
综上所述,您的示例可能已经过简化,以便更容易在 SO 上提问(好主意)。有时,为了清晰、关注点分离、可重用性,或者仅仅是因为您 有 因为您不控制正在侦听它的库,所以让它们分开操作确实有意义。例如react-router-redux
您需要调度特殊操作来进行路由转换。
所以将此视为 "keep it in mind" 建议而不是教条 :)
连接
concat 支持任意数量的参数,因此您只需要其中一个:
const addItemEpic = action$ => action$
.ofType(ADD_ITEM)
.switchMap(action => {
const apiCall$ = Observable.fromPromise(api.addItem(action.payload))
.map(item => actions.loadItem(item))
.catch(err => Observable.of(actions.ajaxError(err)));
return Observable.concat(
Observable.of(actions.updateLayout(true)),
apiCall$,
Observable.of(actions.updateLayout(false))
);
});
// or
const addItemEpic = action$ => action$
.ofType(ADD_ITEM)
.switchMap(action =>
Observable.concat(
Observable.of(actions.updateLayout(true))
Observable.fromPromise(api.addItem(action.payload))
.map(item => actions.loadItem(item))
.catch(err => Observable.of(actions.ajaxError(err))),
Observable.of(actions.updateLayout(false))
)
);
我到处搜索都找不到以下问题的答案...
在我的 addItemEpic 中,我想在发送 ajax 请求之前分派一个 "loading" 操作 - 我不希望操作的值被 returned - 我只是想要执行操作然后 return ajax 响应。 这可以通过 store.dispatch() 轻松实现,如下所示:
export const addItemsEpic = (action$, store) =>
action$.ofType(ADD_ITEM)
.do(() => store.dispatch({
type: 'layout/UPDATE_LAYOUT',
payload: { loading: true }
}))
.switchMap(action => api.addItem(action.payload))
.map(item => loadItem(item))
.do(() => store.dispatch({
type: 'layout/UPDATE_LAYOUT',
payload: { loading: false }
}))
.catch(error => Observable.of({
type: 'AJAX_ERROR',
payload: error,
error: true
}));
然而 store.dispatch() 在 redux-observable 中被弃用和劝阻。
我已经尝试使用几乎所有可能的运算符,但我仍然无法在不破坏下一个链接函数中的 return 值的情况下发送操作。我认为像下面这样的东西应该可以做到:
action$.ofType(ADD_ITEM)
.concatMap(action => Observable.of(
updateLayout({loading: true}),
api.addItem(action.payload)).last())
.map(item => loadItem(item))
但不幸的是这里有 2 个问题:
- 没有调度加载操作(值 returned 作为可观察值)
- api 现在请求 return 包装的承诺而不是可映射的值
我真的很感谢这里的一些帮助或建议,因为我找不到任何方法来让它工作。
提前致谢
第二种形式走在正确的道路上。这个想法是 return 一个 Observable
首先发出加载动作,然后在服务调用之后发出 loaded
动作或 error
动作,最后发出完成加载动作。
const addItemEpic = action$ => action$
.ofType(ADD_ITEM)
.switchMap(action => { // or use mergeMap/concatMap depending on your needs
const apiCall$ = Observable.fromPromise(api.addItem(action.payload))
.map(item => actions.loadItem(item))
.catch(err => Observable.of(actions.ajaxError(err)));
return Observable.concat(
Observable.of(actions.updateLayout(true)),
Observable.concat(apiCall$,
Observable.of(actions.updateLayout(false))));
});
Observable.fromPromise()
将 Promise
转换为 Observable
罗伯特的回答很好而且很正确。以下是我的一些额外想法:
重用现有操作
很多时候,当您发现自己同步执行一个又一个操作时,这表明您的减速器应该只需要第一个。
例如而不是让 layout/UPDATE_LAYOUT
设置 loading: true
,你的 reducer 可以知道只要有 ADD_ITEM
和 LOAD_ITEM
就意味着它应该是 loading: true
。如果这种隐含性不是您的包,您可以使用额外的元数据作为信号而不是操作类型本身。
const layoutReducer = (state = { loading: false }, action) => {
// instead of listening for an action type, we're looking for some other
// metadata by convention. In this case the `layout` property.
if (action.layout) {
return { ...state, ...action.layout };
} else {
return state;
}
};
// the schema you use (payload vs layout vs metadata, etc) is your call
store.dispatch({
type: 'DOESNT_MATTER',
payload: 'whatever',
layout: { loading: true }
});
综上所述,您的示例可能已经过简化,以便更容易在 SO 上提问(好主意)。有时,为了清晰、关注点分离、可重用性,或者仅仅是因为您 有 因为您不控制正在侦听它的库,所以让它们分开操作确实有意义。例如react-router-redux
您需要调度特殊操作来进行路由转换。
所以将此视为 "keep it in mind" 建议而不是教条 :)
连接
concat 支持任意数量的参数,因此您只需要其中一个:
const addItemEpic = action$ => action$
.ofType(ADD_ITEM)
.switchMap(action => {
const apiCall$ = Observable.fromPromise(api.addItem(action.payload))
.map(item => actions.loadItem(item))
.catch(err => Observable.of(actions.ajaxError(err)));
return Observable.concat(
Observable.of(actions.updateLayout(true)),
apiCall$,
Observable.of(actions.updateLayout(false))
);
});
// or
const addItemEpic = action$ => action$
.ofType(ADD_ITEM)
.switchMap(action =>
Observable.concat(
Observable.of(actions.updateLayout(true))
Observable.fromPromise(api.addItem(action.payload))
.map(item => actions.loadItem(item))
.catch(err => Observable.of(actions.ajaxError(err))),
Observable.of(actions.updateLayout(false))
)
);