3

我正在尝试使用 redux-observable 做一个简单的通知系统。我是新手,rxjs所以我很难做到。

我想做的是:

  1. 调度意图以显示通知
  2. 检测意图Epic
  3. 调度插入新通知的操作
  4. 等待 3 秒
  5. 调度另一个删除旧通知的操作

这是我的史诗:

import { NOTIFICATION_DISPLAY_REQUESTED } from '../actions/actionTypes';
import { displayNotification, hideNotification } from '../actions/notifications';

export const requestNotificationEpic = (action$, store) =>
  action$.ofType(NOTIFICATION_DISPLAY_REQUESTED)
    .mapTo(displayNotification(action$.notification))
    .delay(3000)
    .mapTo(hideNotification(action$.notification));

真正发生的NOTIFICATION_DISPLAY_REQUESTED是调度,3 秒后,hideNotification调度。displayNotification永远不会发生。

我可以displayNotification从视图中调度,延迟 3 秒,然后调度hideNotification。但是稍后如果有超过 3 个活动通知,我想在添加新通知之前删除最后一个通知。这就是为什么我displayNotification在这个简单的案例中从史诗内部手动调度。

那么,我该如何实现呢?抱歉,如果这是一个超级简单的问题,我对这一切都很陌生,需要一些帮助。

注意:我知道redux-saga存在,只是这redux-obsevable对我来说更有意义。

4

1 回答 1

4

如果你是 RxJS 的新手,这不是那么简单 :)

前面几件事:

运营商链

Epic 是一个函数,它接受一个动作流并返回一个动作流。行动进来,行动出去。您链接以转换匹配操作的函数称为操作符。链接运营商很像链接花园软管或电源线——价值从一个流向另一个。它也非常类似于仅链接常规函数,third(second(first()))除了 Observables 有一个额外的维度time,因此运算符应用于流经它们的每个值。

因此,如果您说当您忽略源的值而只是将其映射到提供的值时,您首先映射到的事实变得毫无意义stream.mapTo(x).mapTo(y)x.mapTo(y)mapTo

如果改为使用map,它可能会变得更加明显:

stream.map(value => 'a message').map(message => message + '!!!')

更清楚地说,这种操作符的链接是 RxJS,而不是特定于 redux-observable,它更像是使用惯用的 RxJS和少量胶水到 redux 的模式。

action$是一个 Observable(技术上是 ActionsObservable)

参数action$是动作的 Observable,而不是实际动作本身。action$.notification也会如此undefined。这就是人们通常使用美元符号后缀的原因之一,以表示它是这些东西的

考虑只有 2 个动作,而不是 3 个

您的示例向您展示了使用三个操作NOTIFICATION_DISPLAY_REQUESTED和另外两个操作来显示和隐藏通知。在这种情况下,最初的意图动作基本相同,displayNotification()因为它将在另一个之后同步调度。

考虑只有两个动作,一个用于“显示此通知”,另一个用于“隐藏此通知”。虽然这不是一个规则,但它通常可以简化您的代码并提高性能,因为您的减速器不必运行两次。

这就是您的情况(当然,您可以随意命名):

export const displayNotificationEpic = (action$, store) =>
  action$.ofType(DISPLAY_NOTIFICATION)
    .delay(3000)
    .map(action => hideNotification(action.notification));

// UI code kicks it off some how...
store.dispatch(displayNotification('hello world'));

然后您的减速器将收到DISPLAY_NOTIFICATION,然后 3 秒后HIDE_NOTIFICATION(订购任何东西)。

此外,记住redux-observable docs也很重要:

记住:在 reducer 已经收到 Epics 之后,Epics 与正常的 Redux 调度通道一起运行。当您将一个动作映射到另一个动作时,您并没有阻止原始动作到达减速器;那个动作已经通过他们了!

解决方案

虽然我建议在这种情况下只使用两个动作(见上文),但我确实想直接回答你的问题!由于 RxJS 是一个非常灵活的库,因此有很多方法可以完成您的要求。

这里有一对:

一部史诗,使用 concat

concat运算符用于一次订阅所有提供的 Observable,仅在当前一个完成时才移动到下一个。它一次“耗尽”每个 Observable。

如果我们想创建一个发出一个动作的流,等待 3000 毫秒然后发出另一个动作,你可以这样做:

Observable.of(displayNotification(action.notification))
  .concat(
    Observable.of(hideNotification(action.notification))
      .delay(3000)
  )

或这个:

Observable.concat(
  Observable.of(displayNotification(action.notification)),
  Observable.of(hideNotification(action.notification))
    .delay(3000)
)

在这种情况下,它们具有完全相同的效果。关键是我们将 应用于与delay第一个不同的 Observable ——因为我们只想延迟第二个动作。我们隔离他们。

要在你的史诗中使用,你需要一个合并策略运算符,如mergeMap,switchMap等。这些对于学习非常重要,因为它们在 RxJS 中经常使用。

export const requestNotificationEpic = (action$, store) =>
  action$.ofType(NOTIFICATION_DISPLAY_REQUESTED)
    .mergeMap(action =>
      Observable.concat(
        Observable.of(displayNotification(action.notification)),
        Observable.of(hideNotification(action.notification))
          .delay(3000)
      )
    );

两种不同的史诗

另一种方法是创建两个不同的史诗。一个负责将第一秒映射到第二个,另一个负责隐藏前等待 3 秒。

export const requestNotificationEpic = (action$, store) =>
  action$.ofType(NOTIFICATION_DISPLAY_REQUESTED)
    .map(action => displayNotification(action.notification));

export const displayNotificationEpic = (action$, store) =>
  action$.ofType(DISPLAY_NOTIFICATION)
    .delay(3000)
    .map(action => hideNotification(action.notification));

这是有效的,因为史诗可以匹配所有动作,甚至是其他史诗发出的动作!这允许干净的分离、组合和测试。

这个例子(对我来说)更好地证明了这个例子不需要两个意图动作,但可能有你没有提供的要求证明它是合理的。


如果这很令人困惑,我建议先深入研究 RxJS。教程、视频、研讨会等。这只是表面的,它要深入得多,但对于大多数坚持下去的人来说,回报是巨大的。

于 2017-05-12T19:39:52.430 回答