替代直接调用store.dispatch()

时间:2017-12-06 01:10:10

标签: redux redux-observable

由于最新的redux-observable(0.17)是不推荐直接调用store.dispatch(),我想知道如果我需要从redux app外部调度动作,还有什么选择。

示例:

假设我有这个函数初始化本机模块并设置本机处理程序。

const configure = (dispatch) => {
    const printingModule = NativeModules.PrintingManager
    const eventEmitter = new NativeEventEmitter(printingModule)

    eventEmitter.addListener(
        "PrintingManagerNewPrinterConnected",
        (payload) => dispatch({
            type: PRINTER_MANAGER_NEW_PRINTER_CONNECTED,
            payload: {
                macAddress: payload[2],
                connectionType: payload[3],
            },
        }))

    printingModule.initialize()
}

我通常做的是在APP_STARTUP_FINISHED之后我会从observable调用这个函数:

const appStatePrepared = (action$: Object, { dispatch }) =>
    action$.ofType(APP_STATE_PREPARED)
        .switchMap(() => {
            configurePrinters(dispatch)
        })

对此有什么正确的解决方案?

谢谢!

1 个答案:

答案 0 :(得分:5)

使用RxJS时,理想的是组合流。因此,在这种情况下,我们需要了解如何创建"PrintingManagerNewPrinterConnected"事件流,然后我们可以将每个事件映射到他们自己的PRINTER_MANAGER_NEW_PRINTER_CONNECTED action.a

让我们首先学习如何完全自定义。

自定义Observables

创建自己的自定义Observable与创建Promise非常相似。所以说你拥有世界上最简单的Promise,它会立即解析为1号

const items = new Promise(resolve => {
  resolve(1);
});

等效的Observable看起来非常相似

const items = new Observable(observer => {
  observer.next(1);
  observer.complete();
});

从视觉上看,主要区别在于我们没有传递(resolve, reject)个回调,而是给了一个观察者,因为有下一个,错误和完整。

从语义上讲,Observable可以通过调用observer.next多次代表多个值,直到他们调用observer.complete()来表示流的结尾;这与Promises形成对比,Promise只代表单一值。

默认情况下,Observable也是惰性和同步的,而Promise总是渴望和异步。

现在我们已经了解了这一点,并希望将NativeEventEmitter API包含在使用addEventListener

const configurePrinters = () => {
  return new Observable(observer => {
    const printingModule = NativeModules.PrintingManager;
    const eventEmitter = new NativeEventEmitter(printingModule);

    eventEmitter.addListener(
      'PrintingManagerNewPrinterConnected',
      (payload) => observer.next(payload)
    );

    printingModule.initialize();
  });
};

configurePrinters()
  .subscribe(payload => console.log(payload));

这很有效,而且非常简单,但它有一个问题:我们应该在取消订阅时调用removeListener,以便我们自行清理并且不会泄漏内存。

为此,我们需要在自定义Observable中返回订阅。此上下文中的订阅是一个对象,其上有一个unsubscribe()方法,当订阅者取消订阅,触发错误或您的observable完成时,将自动调用该方法。这是你清理的机会。

const items = new Observable(observer => {
  let i = 0;
  const timer = setInterval(() => {
    observer.next(i++);
  }, 1000);

  // return a subscription that has our timer cleanup logic
  return {
    unsubscribe: () => {
      clearInterval(timer);
    }
  };
});

因为返回一个对象有点冗长,RxJS支持一个简写,你只需返回一个本身将被视为unsubscribe方法的函数。

const items = new Observable(observer => {
  let i = 0;
  const timer = setInterval(() => {
    observer.next(i++);
  }, 1000);

  // return an "unsubscribe" function that has our timer cleanup logic
  return () => {
    clearInterval(timer);
  };
});

现在我们可以将它应用于我们的示例,我们希望在调用取消订阅拆解函数时删除我们的侦听器。

const configurePrinters = () => {
  return new Observable(observer => {
    const printingModule = NativeModules.PrintingManager;
    const eventEmitter = new NativeEventEmitter(printingModule);

    const listener = (payload) => observer.next(payload);

    eventEmitter.addListener(
      'PrintingManagerNewPrinterConnected',
      listener
    );

    printingModule.initialize();

    return () => eventEmitter.removeListener(
      'PrintingManagerNewPrinterConnected',
      listener
    );
  });
};

现在让我们把它变成一个可重复使用的效用函数

const fromPrinterEvent = (eventName) => {
  return new Observable(observer => {
    const printingModule = NativeModules.PrintingManager;
    const eventEmitter = new NativeEventEmitter(printingModule);

    const listener = (payload) => observer.next(payload);
    eventEmitter.addListener(eventName, listener);
    printingModule.initialize();

    return () => eventEmitter.removeListener(eventName, listener);
  });
};

fromPrinterEvent('PrintingManagerNewPrinterConnected')
  .subscribe(payload => console.log(payload));

Observable.fromEvent

虽然NativeEventEmitter是反应原生的东西,但它跟在node-style EventEmitter interface之后,RxJS已经附带了一个实用程序助手,可以从它们创建一个Observable来节省你的工作量。 It's called fromEvent,位于Observable.fromEventimport { fromEvent } from 'rxjs/observables/fromEvent'

const fromPrinterEvent = (eventName) => {
  return Observable.defer(() => {
    const printingModule = NativeModules.PrintingManager;
    const eventEmitter = new NativeEventEmitter(printingModule);
    printingModule.initialize();

    return Observable.fromEvent(eventEmitter, eventName);
  });
};

此处我还将其包装在Observable.defer中,以便我们不会创建NativeEventEmitterprintingModule.initialize(),直到有人真正订阅(保持懒惰)。这对您来说可能是必要的,也可能不是,我不知道PrintingManager的作用或行为方式。例如可能需要只创建一个发射器并预先初始化模块。

const printingModule = NativeModules.PrintingManager;
const printerEmitter = new NativeEventEmitter(printingModule);
printingModule.initialize();

const fromPrinterEvent = (eventName) =>
  Observable.fromEvent(printerEmitter, eventName);

所以请记住,我只是在不知道PrintingManager等所做的事情的情况下展示模式。

在redux-observable

中使用它

要在redux-observable中使用它,你的史诗现在与你使用任何其他Observable相同。因此,我们希望将值中的值映射到操作和  mergeMap,switchMap,concatMap或exhaustMap进入我们的顶级流。

这样的事情:

const appStatePrepared = action$ =>
  action$.ofType(APP_STATE_PREPARED)
      .switchMap(() =>
        fromPrinterEvent('PrintingManagerNewPrinterConnected')
          .map(payload => ({
            type: PRINTER_MANAGER_NEW_PRINTER_CONNECTED,
            payload: {
              macAddress: payload[2],
              connectionType: payload[3],
            }
          }))
      );

请记住,许多流(包括我们的自定义fromPrinterEvent('PrintingManagerNewPrinterConnected'))将永远消失,直到您取消订阅它们为止。因此,如果您只想要一个,请使用.take(1)。如果您想在接收其他操作时取消订阅,请使用.takeUntil(action$.ofType(WHATEVER))等正常的RxJS模式。