如何用redux-observable做一个简单的通知系统?

How to do a simple notification system with redux-observable?

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

我想做的是:

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

这是我的史诗:

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 对我来说更有意义。

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

前面的两件事:

运算符链

Epic 是一个函数,它接受一系列操作和 returns 一系列操作。动作进,动作出。您链接以转换匹配操作的函数称为 operators。链接运算符很像链接花园软管或电源线——值从一个流向另一个。它也非常类似于像 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 特有的,它更像是一种使用 idiomatic RxJS 的模式,在 redux 中加入少量胶水。

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

参数action$是动作的可观察对象,而不是实际动作本身。所以 action$.notification 将是 undefined。这就是人们通常使用美元符号后缀的原因之一,表示它是那些东西的 stream

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

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

考虑只有两个动作,一个用于 "show this notification",另一个用于 "hide this notification"。虽然这不是一条规则,但它通常可以简化您的代码并提高性能,因为您的减速器不必 运行 两次。

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

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'));

然后你的 reducers 会收到 DISPLAY_NOTIFICATION 然后 3 秒后 HIDE_NOTIFICATION (随便排序)。

此外,重要的是要记住 redux-observable docs:

REMEMBER: Epics run alongside the normal Redux dispatch channel, after the reducers have already received them. When you map an action to another one, you are not preventing the original action from reaching the reducers; that action has already been through them!

解决方案

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

这里有一对:

一个史诗,使用 concat

concat operator 用于一次订阅所有提供的 Observable,仅当当前一个完成时才移动到下一个。它 "drains" 每个 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——因为我们只想延迟第二个动作。我们隔离他们。

要在你的史诗中使用,你需要一个合并策略运算符,如 mergeMapswitchMap 等。这些对于学好非常重要,因为它们在 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)
      )
    );

两个不同的epics

另一种方法是创建两个不同的 epics。一个负责将第一秒映射到第二秒,另一个负责在隐藏前等待 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));

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

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


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