【问题标题】:using redux-observable epics to decorate data through multiple actions使用 redux-observable 史诗通过多个动作来装饰数据
【发布时间】:2017-02-16 01:39:11
【问题描述】:

我正在为redux-observable 苦苦挣扎,试图弄清楚如何使用此流程创建史诗:

  1. 收听GET_ITEMS_REQUEST 操作
  2. 发送 HTTP 请求以获取一些项目
  3. 获取这些项目的 ID 并发送 GET_ITEM_DETAILS_REQUEST 操作,这将发送另一个 HTTP 请求以获取这些项目的更多详细信息
  4. 使用第二个请求中的详细信息装饰第一个请求中的项目,并调度最终的 GET_ITEMS_SUCCESS 操作,这将更新 redux 状态

从第 3 步到第 4 步是我卡住的地方。我知道如何使用项目 ID 发送 GET_ITEM_DETAILS_REQUEST,但我不知道如何收听/订阅 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'
      });
    });
}

【问题讨论】:

    标签: reactjs redux rxjs redux-observable


    【解决方案1】:

    一种方法是,在您收到项目后,开始收听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!这是一个非常重要的区别,我们通常称之为“隔离您的 Observable 链”。您不希望错误传播得超出应有的范围。

    // 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
        }));
    

    您有时确实希望在外链上捕获一个问题,但这是最后一道防线,通常仅适用于不可恢复的错误。

    【讨论】:

    • 我做了一个重要的更改:我忘记在action$.ofType('GET_ITEM_DETAILS_FULFILLED') 之后添加take(1)。这很重要,否则您的史诗将永远聆听它们,并且 mergeMap 将无限期地积累越来越多的它们。
    • 这看起来很棒,谢谢!什么保证GET_ITEM_DETAILS_FULFILLED 将完全是对立即发送的详细信息请求的响应?如果在详细信息请求返回响应之前发出另一个 GET_ITEM_DETAILS_FULFILLED 怎么办?
    • 顺便说一下,感谢catch() 上的注释。我也一直在为此苦苦挣扎。
    • 另外,take(1).map(...).startWith(...) 的顺序重要吗?
    • 另外,.map(({ details }) => ({ 不应该是 mergeMap 吗?在本地尝试后,使用.map() 会引发Actions must be plain objects 错误,而mergeMap() 似乎可以正常工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-14
    • 2018-01-20
    • 2023-03-29
    • 1970-01-01
    • 2017-03-17
    • 2021-11-13
    • 2019-01-23
    相关资源
    最近更新 更多