使用惯用的redux observable

时间:2017-12-07 04:51:10

标签: redux rxjs rxjs5 redux-observable

给出下载管理器的示例。可以有任意数量的活动下载。

可以调度操作以启动,停止,标记完成并标记任何特定下载的下载进度。

const START_DOWNLOAD = "START_DOWNLOAD";
const startDownload = payload => ({ type: START_DOWNLOAD, payload });

const DOWNLOAD_PROGRESS = "DOWNLOAD_PROGRESS";
const downloadProgress = payload => ({ type: DOWNLOAD_PROGRESS, payload });

const STOP_DOWNLOAD = "STOP_DOWNLOAD";
const stopDownload = payload => ({ type: STOP_DOWNLOAD, payload });

const COMPLETE_DOWNLOAD = "COMPLETE_DOWNLOAD";
const completeDownload = payload => ({ type: COMPLETE_DOWNLOAD payload });

这些操作将包含用于标识下载的ID,并可使用以下reducer修改redux状态:

const downloadReducer = (state = initialState, action) => {
  switch (action.type) {
    case STOP_DOWNLOAD:
      return {
        ...state,
        [action.payload.id]: {
          state: "IDLE",
        },
      };

    case START_DOWNLOAD:
      return {
        ...state,
        [action.payload.id]: {
          state: "IN_PROGRESS",
          progress: 0,
        },
      };

    case DOWNLOAD_PROGRESS:
      return {
        ...state,
        [action.payload.id]: {
          state: "IN_PROGRESS",
          progress: action.payload.progress,
        },
      };

    case COMPLETE_DOWNLOAD:
      return {
        ...state,
        [action.payload.id]: {
          state: "DONE",
          progress: 100,
        },
      };

    default:
      return state;
  }
};

现在出现了如何使用redux observable管理这些操作的异步调度的问题。

例如,我们可以这样做:

const downloadEpic = action$ =>
  action$.ofType(START_DOWNLOAD).mergeMap(action =>
    downloader
    .takeUntil(
      action$.filter(
        stop =>
        stop.type === STOP_DOWNLOAD &&
        stop.payload.id === action.payload.id,
      ),
    )
    .map(progress => {
      if (progress === 100) {
        return completeDownload({
          id: action.payload.id
        });
      } else {
        return downloadProgress({
          id: action.payload.id,
          progress
        });
      }
    }),
  );

这很有效。但是,如果我们想限制允许的活动下载数,该怎么办?我们可以将mergeMap替换为concatMap,一次只允许一次有效下载。或者我们可以提供concurrent parametermergeMap并准确指定我们想要允许的内部下载器可观察的执行次数。

然而,这带来的问题是我们现在无法停止排队下载。

I have created a complete working example that you can try here.

如何以最恰当的方式使用rxjs和redux observable限制和排队下载?

1 个答案:

答案 0 :(得分:3)

我已经关注这个问题几天但是没有时间用完整的代码给你一个可靠的答案,因为它相对复杂。所以相反,我只会给你tl; dr版本比我希望的更好:)

我的直觉告诉我,我会让UI调度一个代表尝试下载而不是真正保证的动作。例如ATTEMPT_DOWNLOAD。史诗会听取此操作,并检查当前活动下载次数是否超过> =最大值,如果是,则会发出一个操作来排队下载而不是启动它。

您的Reducer将存储那些活动的下载ID和下载排队的ID。 e.g。

{ active: ['123', '456', ...etc], queued: ['789'] }

您可以使用哪种方式专门跟踪活动/排队,还要知道它们的计数active.lengthqueued.length

当下载完成某个地方的史诗时会检查是否有任何排队下载,如果是,则将其取消。你究竟是怎么做的主要是个人偏好。例如如果史诗发出DEQUEUE_DOWNLOAD或其他什么。

如果发送了取消操作,则您的Reducer需要同时查看activequeued并删除ID(如果该ID存在)。如果它实际上是活动的而不仅仅是排队,那么处理下载的史诗将会收听取消操作,并且会停止它并执行与上述相同的出列检查。

这有点手绢,但最重要的是:

  • 您的UI组件在尝试之前不应该知道事情将如何或何时排队,尽管通过查看redux状态可以看到事后(例如显示"下载排队"或者无论)
  • 小心不要在多个地方复制下载状态。例如如果它是活跃的,只在您的商店中有一个单一的事实来源,说它是
  • epics在减速器之后运行,使用它对您有利。
  • 可能会有史诗唯一的工作就是听取真正的下载请求并执行它,而不知道有关排队或类似事情的任何事情。这部史诗将被其他多部史诗重复使用。通过直接将其作为函数调用或通过发出类似于您的真实START_DOWNLOAD
  • 的操作来实现

并不总是清楚业务逻辑应该在哪里。例如减压器应该主要是吸气剂/制定者,还是应该有更多自以为是的逻辑并做出决定?在这些情况下,没有很多规则。试着保持一致。

顺便说一下,这完全来自我的直觉。我可能已经发现(或者您可能仍然发现)在实践中这不是一个好的解决方案。只是给我最初的想法!