RxJS 非常强大,各种操作符连接在一起便能让数据流动到需要用到它的地方,有人甚至觉得 RxJS 是魔法;
然而,太阳底下无新事,本教程将一步步揭开 RxJS 的神秘面纱;
这篇文章会通过 10 个有趣的小 demo 渐进式的实现 RxJS 的核心功能,其中包括:
- 类:
Observable
的实现 - 类的方法:
subscribe
,pipe
的实现 - 创建类操作符:
of
,from
,fromEvent
,interval
,timer
的实现 - 过滤类操作符:
filter
,take
的实现 - 工具类操作符:
tap
的实现 - 组合类操作符:
merge
的实现
其中还包括 RxJS v5 链式调用 和 RxJS v6 通过 pipe 来调用 的各自实现。
而这一切,只有200行不到的代码,如果感兴趣,开始你的愉快之旅吧!
写在前面
为了保证阅读效果,建议读者边阅读边动手实操,点击这里可以下载相应的代码。
1、实现一个 Observable
RxJS 的一切起源与 Observable,Observable 是 RxJS 世界的基石,没有它,响应式无重谈起。
Observable 表示一个可观察对象,他表示一个可调用的未来值或事件的集合。
比如有以下代码:
import {
Observable } from 'rxjs';
const dataStream$ = new Observable(observer => {
observer.next(1);
setTimeout(() => {
observer.next(2);
observer.complete();
}, 1000)
observer.next(3);
});
const observer = {
next: x => console.log(x),
error: err => console.error(err),
complete: () => console.log('done'),
}
dataStream$.subscribe(observer);
这段代码引用的是官方的 Observable
, 它在运行后会首先打印一个1,接着打印一个3,隔一秒后会再打印一个2,最后运行结束
仔细观察 Observable
方法,他会接受一个方法传进它的构造函数,这个方法接受一个对象,对象上有 next
, error
, complete
等属性,但是这个对象是 Observable
实例在调用 subscribe
方法时才传进去的:
有了上面的思路,我们可以大胆的构造出自己的 Observable
, 如下:
export class Observable {
_subscribe;
constructor(subscribe) {
this._subscribe = subscribe;
}
subscribe(observer) {
this._subscribe(observer);
}
}
把官方的 Observable
替换成自己的 Observable
会发现输出没什么差异。
没错,Observable
的核心就是这么简单。到现在,我们只用了9行代码实现便了Rxjs的核心概念之一 —— Observable
。
2、实现创建类操作符 of
创建类操作符中,最容易理解的莫过于 of
,那么我们就先实现 of
操作符。
比如有如下代码:
import {
of } from 'rxjs';
const dataStream$ = of(1, 2, 3)
const observer = {
next: x => console.log(x),
error: err => console.error(err),
complete: () => console.log('done'),
}
dataStream$.subscribe(observer);
它在运行后会首先打印一个1,接着打印一个2,再会打印一个3,最后运行结束。
有了前面自己实现的 Observable
, of
的实现就会变得非常简单,它实际上只是 Observable
外套了一层包装,本质上还是 Observable
,实现如下:
export function of(...args) {
return new Observable(observer => {
args.forEach(arg => {
observer.next(arg);
})
observer.complete();
})
}
把官方的 of
替换成自己的 of
,再配上自己实现的 Observable
,我们会发现输出和官方一致。
3、Observable.subscribe 可以传人一个方法作为参数
官方 Observable
的 subscribe
可以传入一个函数进去,这样的话写起来会清爽很多,如下:
import {
of } from 'rxjs';
const dataStream$ = of(1, 2, 3)
dataStream$.subscribe(console.log);
为了我们的 Observable
也能这样的好用,我们可以将 subscribe
适当的改造一下,如下:
export class Observable {
_subscribe;
constructor(subscribe) {
this._subscribe = subscribe;
}
subscribe(observer) {
const defaultObserver = {
next: () => {
},
error: () => {
},
complete: () => {
}
}
if (typeof observer === 'function') {
return this._subscribe({
...defaultObserver, next: observer });
} else {
return this._subscribe({
...defaultObserver, ...observer });
}
}
}
这样我们的 subscribe
也能只传一个方法进去了,变得相当好用。
4、实现创建类操作符 fromEvent
但是,Rxjs 核心要解决的是数据流传输的问题,很多时候,我们的数据源头来自用户的人机交互,比如说点击按钮,这样的话就不得不用到 fromEvent
,比如如下代码:
import {
fromEvent } from 'rxjs';
import {
JSDOM } from 'jsdom';
const element = new JSDOM(`<div>Hello world</div>`).window.document.querySelector('div');
const source$ = fromEvent(element, 'click');
source$.subscribe(console.log);
setTimeout(() => {
element.click()
}, 1000)
为了方便对比和测试,我们引用了 jsdom
,它的作用是在 node
端可以做一些 dom 的相关操作。
以上代码渲染了一个 Hello world
的元素盒子,并且在一秒钟之后会点击这个盒子。与此同时,我们又使用了 Rxjs 中的 fromEvent
来监听盒子的事件。
为了实现自己的fromEvent
,我们来分析一下 fromEvent
所需要的参数,第一个传的是 dom 元素的实例,第二个则是事件的类型,于是可以猜到, fromEvent
内部本质上还是通过原生的 addEventListener
来实现的。而且需要注意到,除非自己手动取消订阅,否则fromEvent
创造的对象永远不会结束,根据这个推测,我们能猜到它的内部很有可能只有 next
方法。
有了上述的推断,我们很容易就写出了一个 fromEvent
方法,如下:
export function fromEvent(element, event) {
return new Observable(observer => {
const handler = e => observer.next(e);
element.addEventListener(event, handler);
});
}
连我都惊讶它的实现居然如此简短,但是运行的效果和官方的效果完全一致。
5、实现创建类操作符 from、interval、timer
有了上面构造 of
、fromEvent
的基础,那么 from
、interval
、timer
也将不在话下。
例如,interval
操作符可以这样实现:
export function interval(delay) {
return new Observable(observer => {
let index = 0;
setInterval((() => {
observer.next(index++)
}), delay)
})
}
timer
操作符可以这样实现:
export function timer(delay) {
return new Observable(observer => {
setTimeout((() => {
observer.next