事件桥接

RxJS 提供工厂方法来桥接 DOM 或 Node.js 中已存在的异步数据源,所以,你可以使用丰富的创作、过滤和资源管理功能对RxJS提供的任何类型的数据流进行操作。这篇文章探讨 fromEventfromEventPattern操作符,它允许导入一个 DOM 或者普通事件到 RxJS 的数据流。每次引发事件时,一个 OnNext 消息将传递到数据流。然后,可以像其他任何数据流一样操作事件数据流。

RxJS 不打算取代现有的异步编程模型如 Promisescallbacks。但是,当你尝试组合事件, RxJS的工厂方法会提供简便的方法给你,你完全感受不到当前使用了何种编程模式。这真的很方便维护(比如取消订阅)和筛选(比如选择合适的数据)数据源。在本节和下节中,你可以尝试 RxJS 的这些特性如何协助你完成异步编程。

自然,RxJS 支持一批库和他们的勾子函数去使用他们的事件系统,比如 jQuery, Zepto.js, AngularJS, Ember.jsBackbone.js。这种行为,不管怎样只能重写本地绑定。默认情况下, RxJS 也支持 Node.js EventEmitter 的事件勾子。

将一个 DOM 事件转换成 RxJS 数据流

接下来这个例子为鼠标移动事件创建了一个 DOM 事件操作,并且在页面上打印出鼠标的坐标。

var result = document.getElementById('result');

document.addEventListener('mousemove', e => result.innerHTML = e.clientX + ', ' + e.clientY, false);

导入一个事件到 RxJS, 你可以使用 fromEvent 操作符,并且传入被桥接的事件参数。然后它会将事件转换成数据流。

下面这个例子,我们将 DOM 的 mousemove 事件流转换成事件流(可观察对象)。每次鼠标移动事件被触发时,订阅都会接收到一个 onNext 事件。然后我们可以检查这种通知的事件参数并获得鼠标的坐标。

var result = document.getElementById('result');

var source = Rx.Observable.fromEvent(document, 'mousemove');

var subscription = source.subscribe(e => result.innerHTML = e.clientX + ', ' + e.clientY);

在这个例子中要注意,(鼠标)移动变成一个数据流以便我们进一步操作。 Querying Observable Sequences 这篇文章将会展示如何将该序列投射到点类型集合中并筛选其内容,以便应用程序只接收满足一定条件的值。

事件处理程序的销毁由 subscribe 方法返回的 Disposable 对象处理。调用 dispose 将会释放由该序列所使用的所有资源,包括底层事件处理程序。这本质上是取消订阅事件。

fromEvent 方法还支持向多个项目添加事件处理程序,比如一整个 DOM 节点列表。下面这个例子将会给列表中的每个元素添加 'click' 事件。

var result = document.getElementById('result');
var sources = document.querySelectorAll('div');

var source = Rx.Observable.fromEvent(sources, 'click');

var subscription = source.subscribe(e => result.innerHTML = e.clientX + ', ' + e.clientY);

另外,fromEvent 也支持类库,像 jQuery, Zepto.js, AngularJS, Ember.js and Backbone.js

var $result = $('#result');
var $sources = $('div');

var source = Rx.Observable.fromEvent($sources, 'click');

var subscription = source.subscribe(e => $result.html(e.clientX + ', ' + e.clientY));

如果表现不如预期,你可以通过设置 Rx.config.useNativeEventstrue 去重写它,这会无视任何类库。

// 只使用原生事件,尽管引用了 jQuery
Rx.config.useNativeEvents = true;

// 只使用原生事件
var result = document.getElementById('result');

var source = Rx.Observable.fromEvent(document, 'mousemove');

var subscription = source.subscribe(e => result.innerHTML = e.clientX + ', ' + e.clientY);

另外,您可以轻松地给事件系统的事件添加许多快捷方式,比如 mousemove, 甚至是 Pointer and Touch 事件。

Rx.dom = {};

var events = "blur focus focusin focusout load resize scroll unload click dblclick " +
  "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
  "change select submit keydown keypress keyup error contextmenu";

if (root.PointerEvent) {
  events += " pointerdown pointerup pointermove pointerover pointerout pointerenter pointerleave";
}

if (root.TouchEvent) {
  events += " touchstart touchend touchmove touchcancel";
}

events.split(' ').forEach(e => {
  Rx.dom[e] = (element, selector) => Rx.Observable.fromEvent(element, e, selector)
});

现在我们可以重写单个鼠标拖拽事件:

var draggable = document.getElementById('draggable');

var mousedrag = Rx.dom.mousedown(draggable).flatMap(md => {
  md.preventDefault();

  var start = getLocation(md);

  return Rx.dom.mousemove(document)
    .map(mm => getDelta(start, mm))
    .takeUntil(Rx.dom.mouseup(draggable));
});

注意这在 RxJS-DOM 项目中已经可用,但你自己实现也只需要很少量的代码。

将 Node.js 事件转换成 RxJS 数据流

Node.js 也支持类似 EventEmitter:

var Rx = require('rx'),
  EventEmitter = require('events').EventEmitter;

var eventEmitter = new EventEmitter();

var source = Rx.Observable.fromEvent(eventEmitter, 'data')

var subscription = source.subscribe(data => console.log('data: ' + data));

eventEmitter.emit('data', 'foo');
// => data: foo

使用 FromEventPattern 桥接自定义事件

下面有一个使用类库实现事件订阅和退订的实例。fromEventPattern 方法就是为了这个目的而创建的,用来桥接这些自定义事件。

举个例子,你可以想使用 jQuery on 方法去桥接。我们可以将下列代码转换为基于表格行单击的 alert。

$( "#dataTable tbody" ).on('click', 'tr', e => alert($( e.target ).text()));

使用 fromEventPattern 方法转换后的代码看起来像下面这样。每个函数在处理函数中传递,允许您调用 onoff 方法来正确处理事件的处理。

var $tbody = $('#dataTable tbody');

var source = Rx.Observable.fromEventPattern(
  function addHandler (h) { $tbody.on('click', 'tr', h); },
  function delHandler (h) { $tbody.off('click', 'tr', h); });

var subscription = source.subscribe(e => alert($( e.target ).text()));

除了这种常用的支持外,我们也支持 addHandler 返回一个对象,它可以通过 removeHandler 去完全退订。在这个例子中,我们将使用 Dojo Toolkiton 模块。

require(['dojo/on', 'dojo/dom', 'rx', 'rx.async', 'rx.binding'], (on, dom, rx) => {

    var input = dom.byId('input');

    var source = Rx.Observable.fromEventPattern(
        function addHandler (h) {
            return on(input, 'click', h);
        },
        function delHandler (_, signal) {
            signal.remove();
        }
    );

    var subscription = source.subscribe(
        x => console.log('Next: Clicked!'),
        err => console.log('Error: ' + err),
        () => console.log('Completed'));

    on.emit(input, 'click');
    // => Next: Clicked!
});

相关内容

概念