響應式編程是一種思想迁霎,是一種模式肉盹,就像 if else 和 for while 一樣烹看,是所有編程語言的基本單元国拇。為什么會這樣類比?因為現(xiàn)在基本所有的編程語言都有了自己的 Rx 實現(xiàn)惯殊,比如 RxJava, Rx.Net, RxJS, RxGo, RxDart等等酱吝,至少我們常用的語言都有自己的 Rx。再舉個例子土思,比如斐波那契數(shù)列的實現(xiàn)和編程語言無關對么变逃?不同點就是各語言的 if 語句雹食、for 循環(huán)語句等等的寫法有不同恢着。Rx 也一樣染乌,比如我使用 RxJS 實現(xiàn)了按鈕的雙擊操作慎陵,我可以把代碼拷貝到 Java 中陋葡,修改一下操作符的名稱谭企,使用方式就好了祠墅。
響應式編程同樣也是實現(xiàn)組合式編程和積木式編程的最佳實踐冻璃。使用的越多响谓,你才能體會響應式編程的強大和美妙之處。理論是根基省艳,實戰(zhàn)才有價值娘纷,下面我們就來看看如何用 RxJS 實現(xiàn)按鈕的雙擊效果。
雖然 DOM 中已經(jīng)有了雙擊事件類型跋炕,作為練習赖晶,讓我們假設只有單擊事件可以使用。
如果不使用 RxJS 我們將會怎么做辐烂?
- counter 變量計數(shù)
- addEventListener 注冊單擊事件
- setTimeout 來判斷時間間隔遏插。
讓我們看看 RxJS 如何實現(xiàn)。我們知道 Rx 是事件流纠修,單擊事件流可以想象成從一根管子胳嘲,單擊事件可以想象成一個珠子,一發(fā)生單擊事件扣草,這個珠子就從管子的一頭流入了牛,從另一頭流出颜屠。像下面這樣:
e----
-e---
--e--
---e-
----e
下面看看怎么用 RxJS 實現(xiàn)這根管子:
如何使用單擊事件流實現(xiàn)雙擊事件流呢?管子好說鹰祸,關鍵是里面的珠子甫窟。這里要用到 4 個 RxJS 操作符。先簡要說下操作符蛙婴。我覺得操作符可以類比為中間件蕴坪,攔截函數(shù)等等,后臺同學好理解敬锐,前臺同學不理解的話可以去看看 koa 框架背传。操作符接收一根管子,監(jiān)控管子里的珠子并做完操作台夺,然后重新生成一根管子径玖,里面存放操作完的珠子。
throttleTime:throttle 的中文翻譯是節(jié)流閥的意思颤介。它的參數(shù)是一段時間梳星。它的作用在時間參數(shù)范圍內(nèi)的珠子可以過,時間一到閥門就關上了滚朵,誰也過不去了冤灾。當?shù)谝粋€珠子經(jīng)過它時,它就開始計時辕近。
buffer:它的參數(shù)是一個事件流或者說管子韵吨。它的作用是收集這根管子流出的珠子,放進一個袋子里移宅,這個袋子就是數(shù)組归粉。
map:對經(jīng)過它的珠子做處理。
filter:對經(jīng)過它的珠子做過濾漏峰,滿足要求的珠子才能過去糠悼。
下面是實現(xiàn):
import React, { useRef, useEffect } from "react";
import { fromEvent } from "rxjs";
import { buffer, filter, map, throttleTime } from "rxjs/operators";
// throttleTime 操作符的第二個參數(shù),其實默認值也是 async浅乔,那為什么還要傳入呢倔喂?因為假如你有三個參數(shù),傳入了第一個和第三個靖苇,第二個就必須傳了席噩,js 無法忽略中間的參數(shù)不填。
import { async } from "rxjs/internal/scheduler/async";
export default function App() {
const btnRef = useRef(null);
// 這個是 throttleTime 操作符的第三個參數(shù)顾复,默認值 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>
);
}
解釋說明:
- 快速地在按鈕上點擊兩次芯砸,將產(chǎn)生兩個珠子萧芙,也就是 btnClick$ 事件流里面有兩個 click 事件给梅。
- 我們把這根管子交給了 buffer 操作符,然而 buffer 對這根管子不感興趣双揪,它需要另外一個管子动羽;這另外一根管子也是基于單擊事件流生成的,只不過加了個閥門渔期,里面有個定時器运吓,當你第一次 click 的時候,定時器開始計時疯趟,計時期間的 click 事件都可以進到管子里拘哨;當定時器停止時,click 事件將無法進到管子里信峻。定時器歸零倦青,直到你又一次 click 再開始計時。
- 我們把上一步的管子交給了 buffer盹舞,buffer 把這根管子里的珠子放進袋子产镐,也就是數(shù)組中。比如交給 buffer 之前的管子里有兩個珠子踢步,交給 buffer 之后癣亚,buffer 把這倆數(shù)字放進了袋子,放進了一根新管子获印。
- 這一步 map 操作符拿到了管子述雾,它算出袋子里的珠子數(shù)量,放到另一根新管子里蓬豁,新管子里放的就是珠子的數(shù)量绰咽。
- filter 操作符拿到前面?zhèn)鬟^來的管子,它對里面的數(shù)字有意見地粪,不等于 2 的都扔了,把等于 2 的放進了另一根管子琐谤,傳遞下去蟆技。
- 這一步?jīng)]有操作符了,因此 filter 返回的管子給了 btnDblClick$ 變量保存斗忌。
綜上质礼,只要 btnDlbClick$ 流出的事件,就是雙擊事件织阳,明白了嗎眶蕉?不明白請關注微信公眾號“讀一讀我”哦。