vlambda博客
学习文章列表

响应式编程实战——新版RxJS实现真正双击事件流

响应式编程是一种思想,是一种模式,就像 if else 和 for while 一样,是所有编程语言的基本单元。为什么会这样类比?因为现在基本所有的编程语言都有了自己的 Rx 实现,比如 RxJava, Rx.Net, RxJS, RxGo, RxDart等等,至少我们常用的语言都有自己的 Rx。再举个例子,比如斐波那契数列的实现和编程语言无关对么?不同点就是各语言的 if 语句、for 循环语句等等的写法有不同。Rx 也一样,比如我使用 RxJS 实现了按钮的双击操作,我可以把代码拷贝到 Java 中,修改一下操作符的名称,使用方式就好了。

响应式编程同样也是实现组合式编程和积木式编程的最佳实践。使用的越多,你才能体会响应式编程的强大和美妙之处。理论是根基,实战才有价值,下面我们就来看看如何用 RxJS 实现按钮的双击效果。

虽然 DOM 中已经有了双击事件类型,作为练习,让我们假设只有单击事件可以使用。

如果不使用 RxJS 我们将会怎么做?

  1. counter 变量计数

  2. addEventListener 注册单击事件

  3. setTimeout 来判断时间间隔。

让我们看看 RxJS 如何实现。我们知道 Rx 是事件流,单击事件流可以想象成从一根管子,单击事件可以想象成一个珠子,一发生单击事件,这个珠子就从管子的一头流入,从另一头流出。像下面这样:

e----

-e---

--e--

---e-

----e 

下面看看怎么用 RxJS 实现这根管子:

如何使用单击事件流实现双击事件流呢?管子好说,关键是里面的珠子。这里要用到 4 个 RxJS 操作符。先简要说下操作符。我觉得操作符可以类比为中间件,拦截函数等等,后台同学好理解,前台同学不理解的话可以去看看 koa 框架。操作符接收一根管子,监控管子里的珠子并做完操作,然后重新生成一根管子,里面存放操作完的珠子。

throttleTime:throttle 的中文翻译是节流阀的意思。它的参数是一段时间。它的作用在时间参数范围内的珠子可以过,时间一到阀门就关上了,谁也过不去了。当第一个珠子经过它时,它就开始计时。

buffer:它的参数是一个事件流或者说管子。它的作用是收集这根管子流出的珠子,放进一个袋子里,这个袋子就是数组。

map:对经过它的珠子做处理。

filter:对经过它的珠子做过滤,满足要求的珠子才能过去。

下面是实现:

import React, { useRef, useEffect } from "react";

import { fromEvent } from "rxjs";
import { buffer, filter, map, throttleTime } from "rxjs/operators";

// throttleTime 操作符的第二个参数,其实默认值也是 async,那为什么还要传入呢?因为假如你有三个参数,传入了第一个和第三个,第二个就必须传了,js 无法忽略中间的参数不填。
import { async } from "rxjs/internal/scheduler/async";

export default function App() {
const btnRef = useRef(null);

// 这个是 throttleTime 操作符的第三个参数,默认值 leading 为 true,trailing 为 false。
const throttleConfig = {
leading: false,
trailing: true
}

useEffect(() => {
const btnClick$ = fromEvent(btnRef.current, "click");

const throttled$ = btnClick$.pipe(throttleTime(250, async ,throttleConfig))

const btnDblClick$ = btnClick$.pipe(
buffer(throttled$),
map(arr => arr.length),
filter(len => len === 2),
);

const subscription = btnDblClick$.subscribe(() => {
console.log("双击啦");
});
return () => {
subscription.unsubscribe();
};
});

return (
<div className="App">
<button ref={btnRef}>我是一个按钮</button>
</div>

);
}

解释说明:

  1. 快速地在按钮上点击两次,将产生两个珠子,也就是 btnClick$ 事件流里面有两个 click 事件。

  2. 我们把这根管子交给了 buffer 操作符,然而 buffer 对这根管子不感兴趣,它需要另外一个管子;这另外一根管子也是基于单击事件流生成的,只不过加了个阀门,里面有个定时器,当你第一次 click 的时候,定时器开始计时,计时期间的 click 事件都可以进到管子里;当定时器停止时,click 事件将无法进到管子里。定时器归零,直到你又一次 click 再开始计时。

  3. 我们把上一步的管子交给了 buffer,buffer 把这根管子里的珠子放进袋子,也就是数组中。比如交给 buffer 之前的管子里有两个珠子,交给 buffer 之后,buffer 把这俩数字放进了袋子,放进了一根新管子。

  4. 这一步 map 操作符拿到了管子,它算出袋子里的珠子数量,放到另一根新管子里,新管子里放的就是珠子的数量。

  5. filter 操作符拿到前面传过来的管子,它对里面的数字有意见,不等于 2 的都扔了,把等于 2 的放进了另一根管子,传递下去。

  6. 这一步没有操作符了,因此 filter 返回的管子给了 btnDblClick$ 变量保存。