使用 redux-observable epics 通过多个操作来装饰数据
using redux-observable epics to decorate data through multiple actions
我正在为 redux-observable
苦苦挣扎,试图弄清楚如何使用此流程创建史诗:
- 监听
GET_ITEMS_REQUEST
动作
- 发送 HTTP 请求以获取一些项目
- 获取这些项目的 ID 并调度一个
GET_ITEM_DETAILS_REQUEST
操作,该操作将发送另一个 HTTP 请求以获取这些项目的更多详细信息
- 用第二个请求的详细信息装饰第一个请求的项目并分派最终的
GET_ITEMS_SUCCESS
操作,这将更新 redux 状态
从第 3 步到第 4 步是我卡住的地方。我知道如何使用项目 ID 发送 GET_ITEM_DETAILS_REQUEST
,但我不知道如何 listen/subscribe 到 GET_ITEM_DETAILS_REQUEST
操作以获得项目详细信息响应。
到目前为止,我有以下内容:
function getItemsEpic(action$) {
return action$
// step 1
.ofType('GET_ITEMS_REQUEST')
.mergeMap(() => {
// step 2
return Observable.from(Api.getItems())
})
.mergeMap((items) => {
// step 3
const itemIds = items.map((item) => item.id);
return Observable.of({
type: 'GET_ITEM_DETAILS_REQUEST',
ids: itemIds
});
})
// ... what now?
.catch(() => {
return Observable.of({
type: 'GET_ITEMS_FAILURE'
});
});
}
一种方法是,在收到物品后,开始监听 GET_ITEM_DETAILS_FULFILLED
,然后立即使用 startWith()
开始 GET_ITEM_DETAILS_REQUEST
。另一个史诗将查找细节并发出 GET_ITEM_DETAILS_FULFILLED
我们的另一个史诗正在耐心等待,然后将两者(项目 + 细节)压缩在一起。
const getItemDetailsEpic = action$ =>
action$
.ofType('GET_ITEM_DETAILS_REQUEST')
.mergeMap(({ ids }) =>
Observable.from(Api.getItemDetails(ids))
.map(details => ({
type: 'GET_ITEM_DETAILS_FULFILLED',
details
}))
);
const getItemsEpic = action$ =>
action$
.ofType('GET_ITEMS_REQUEST')
.mergeMap(() =>
Observable.from(Api.getItems())
.mergeMap(items =>
action$.ofType('GET_ITEM_DETAILS_FULFILLED')
.take(1) // don't listen forever! IMPORTANT!
.map(({ details }) => ({
type: 'GET_ITEMS_SUCCESS',
items: items.map((item, i) => ({
...item,
detail: details[i]
// or the more "safe" `details.find(detail => detail.id === item.id)`
// if your data structure allows. Might not be necessary if the
// order is guaranteed to be the same
}))
}))
.startWith({
type: 'GET_ITEM_DETAILS_REQUEST',
ids: items.map(item => item.id)
})
)
);
另外,我注意到你把你的 catch()
放在了外部 Observable 链上。这可能不会完全按照您的意愿进行。当错误到达顶链时,您的整个史诗将被终止——它将不再监听未来 GET_ITEMS_REQUEST
!这是一个非常重要的区别,我们通常称之为 "isolating your Observable chains"。您不希望错误传播得比它们应该传播的更远。
// GOOD
const somethingEpic = action$ =>
action$.ofType('SOMETHING')
.mergeMap(() =>
somethingThatMayFail()
.catch(e => Observable.of({
type: 'STUFF_BROKE_YO',
payload: e,
error: true
}))
);
// NOT THE SAME THING!
const somethingEpic = action$ =>
action$.ofType('SOMETHING')
.mergeMap(() =>
somethingThatMayFail()
)
.catch(e => Observable.of({
type: 'STUFF_BROKE_YO',
payload: e,
error: true
}));
有时您确实希望在外链上有一个问题,但这是最后一搏,通常只针对无法恢复的错误。
我正在为 redux-observable
苦苦挣扎,试图弄清楚如何使用此流程创建史诗:
- 监听
GET_ITEMS_REQUEST
动作 - 发送 HTTP 请求以获取一些项目
- 获取这些项目的 ID 并调度一个
GET_ITEM_DETAILS_REQUEST
操作,该操作将发送另一个 HTTP 请求以获取这些项目的更多详细信息 - 用第二个请求的详细信息装饰第一个请求的项目并分派最终的
GET_ITEMS_SUCCESS
操作,这将更新 redux 状态
从第 3 步到第 4 步是我卡住的地方。我知道如何使用项目 ID 发送 GET_ITEM_DETAILS_REQUEST
,但我不知道如何 listen/subscribe 到 GET_ITEM_DETAILS_REQUEST
操作以获得项目详细信息响应。
到目前为止,我有以下内容:
function getItemsEpic(action$) {
return action$
// step 1
.ofType('GET_ITEMS_REQUEST')
.mergeMap(() => {
// step 2
return Observable.from(Api.getItems())
})
.mergeMap((items) => {
// step 3
const itemIds = items.map((item) => item.id);
return Observable.of({
type: 'GET_ITEM_DETAILS_REQUEST',
ids: itemIds
});
})
// ... what now?
.catch(() => {
return Observable.of({
type: 'GET_ITEMS_FAILURE'
});
});
}
一种方法是,在收到物品后,开始监听 GET_ITEM_DETAILS_FULFILLED
,然后立即使用 startWith()
开始 GET_ITEM_DETAILS_REQUEST
。另一个史诗将查找细节并发出 GET_ITEM_DETAILS_FULFILLED
我们的另一个史诗正在耐心等待,然后将两者(项目 + 细节)压缩在一起。
const getItemDetailsEpic = action$ =>
action$
.ofType('GET_ITEM_DETAILS_REQUEST')
.mergeMap(({ ids }) =>
Observable.from(Api.getItemDetails(ids))
.map(details => ({
type: 'GET_ITEM_DETAILS_FULFILLED',
details
}))
);
const getItemsEpic = action$ =>
action$
.ofType('GET_ITEMS_REQUEST')
.mergeMap(() =>
Observable.from(Api.getItems())
.mergeMap(items =>
action$.ofType('GET_ITEM_DETAILS_FULFILLED')
.take(1) // don't listen forever! IMPORTANT!
.map(({ details }) => ({
type: 'GET_ITEMS_SUCCESS',
items: items.map((item, i) => ({
...item,
detail: details[i]
// or the more "safe" `details.find(detail => detail.id === item.id)`
// if your data structure allows. Might not be necessary if the
// order is guaranteed to be the same
}))
}))
.startWith({
type: 'GET_ITEM_DETAILS_REQUEST',
ids: items.map(item => item.id)
})
)
);
另外,我注意到你把你的 catch()
放在了外部 Observable 链上。这可能不会完全按照您的意愿进行。当错误到达顶链时,您的整个史诗将被终止——它将不再监听未来 GET_ITEMS_REQUEST
!这是一个非常重要的区别,我们通常称之为 "isolating your Observable chains"。您不希望错误传播得比它们应该传播的更远。
// GOOD
const somethingEpic = action$ =>
action$.ofType('SOMETHING')
.mergeMap(() =>
somethingThatMayFail()
.catch(e => Observable.of({
type: 'STUFF_BROKE_YO',
payload: e,
error: true
}))
);
// NOT THE SAME THING!
const somethingEpic = action$ =>
action$.ofType('SOMETHING')
.mergeMap(() =>
somethingThatMayFail()
)
.catch(e => Observable.of({
type: 'STUFF_BROKE_YO',
payload: e,
error: true
}));
有时您确实希望在外链上有一个问题,但这是最后一搏,通常只针对无法恢复的错误。