響應式編程實戰(zhàn)—— RxJS 改變事件流與合并事件流

今天我們來優(yōu)化一下之前的程序惩猫。在 scan 中我們以匿名函數(shù)的形式對一個對象的屬性了進行了加 1 操作蛤签,我們可以把這個匿名函數(shù)變成具名函數(shù)焰望,這樣做更加靈活,復用性也更佳宦搬,對嗎锄列?因此程序變成了這樣:

const addOne = (acc) => ({count: acc.count + 1})
startBtnClick$
      .pipe(
        switchMapTo(intervalCanBeStopped$),
        startWith({count:0}),
        scan(addOne),
      )

如果我們現(xiàn)在想在不改變從程序結構的情況下,點擊開始按鈕后計時器從 0 開始計數(shù)該怎么做绎橘?我們先來測試下:

const reset = (acc) => ({count: 0})
startBtnClick$
      .pipe(
        switchMapTo(intervalCanBeStopped$),
        startWith({count:0}),
        scan(reset),
      )

當我們點擊開始按鈕時,會發(fā)現(xiàn)程序一直輸出 0,說明重置生效了煤辨。到這里裳涛,我們總結出傳遞給 scan 不同的行為會有不同的結果。然而有什么辦法可以不用我們手動拷貝粘貼众辨,而是通過某個操作符來完成呢端三?

mapTo:這個操作符接收一個參數(shù),將原事件流中的事件替換成這個參數(shù)鹃彻。

const addOne = (acc) => ({count: acc.count + 1})
startBtnClick$
      .pipe(
        switchMapTo(intervalCanBeStopped$),
            mapTo(addOne),
        startWith({count:0}),
        scan(/*todo*/),
      )

通過 mapTo 操作符郊闯,我們把時間間隔事件流中的事件都變成了 addOne 函數(shù),也就是說傳遞給 scan 的是一個函數(shù)蛛株,scan 中的累積函數(shù)該怎樣寫呢团赁?

const addOne = (acc) => ({count: acc.count + 1})
startBtnClick$
      .pipe(
        switchMapTo(intervalCanBeStopped$),
            mapTo(addOne),
        startWith({count:0}),
        scan((acc, curr) => curr(acc)),
      )

我們解釋一下。首先到達 scan 操作符的事件為 startWith 中的參數(shù)泳挥,也就是 {count: 0}然痊,也就是說,累積函數(shù)第一次運行的返回值為 {count: 0}屉符,這個返回值將作為下一次運行的 acc 參數(shù)剧浸。搞清楚這一點,我們再來看第二個到達 scan 操作符的事件是什么矗钟,很明顯是 addOne 函數(shù)唆香,累積函數(shù)中的 curr 參數(shù)將被賦值為這個函數(shù)。現(xiàn)在累積函數(shù)的參數(shù)都已經(jīng)確定了吨艇,返回值該怎么寫呢躬它?很明顯,把 acc 作為參數(shù)傳遞給 curr 函數(shù)东涡,計算出返回值冯吓,也就是再下一次的 acc 的值,再下一次到來的還是 addOne 函數(shù)疮跑,如果不停止组贺,將一直執(zhí)行上面的操作,也就是加 1操作祖娘。這個 scan 做的事情像不像 redux 做的事情失尖?

接下來,該把重置按鈕加上了渐苏。Rx 編程模型中最有趣的事情來了掀潮,搭積木。我們該如何把 resetBtnClick$琼富,也就是重置事件流和原來的事件流拼在一起仪吧。

我們知道加 1 操作是由時間間隔流變換而來的,重置按鈕做的是清零操作鞠眉,也就是說邑商,重置事件流至少要放在 mapTo 做轉換之前(先不考慮轉換操作)摄咆,然后 mapTo 根據(jù)到達的事件做判斷,是加 1 還是重置人断,對么?那說明朝蜘,重置流應該和時間間隔流應該屬于同一個事件流恶迈。merge 操作符恰恰就是干這個的。

merge:通過查看官方文檔谱醇,我們會發(fā)現(xiàn) merge 操作符有多個重載實現(xiàn)暇仲。我們用到的是最基本的傳給它多個事件流參數(shù)。

startBtnClick$
      .pipe(
        switchMapTo(merge(intervalCanBeStopped$, resetBtnClick$)),
        mapTo(addOne),
        startWith({ count: 0 }),
        scan((acc, current) => current(acc))
      )

實際運行效果肯定是不對的副渴,因為根本就沒有重置操作奈附。點擊重置按鈕進行只是加 1 操作而已。事件流合并到了一起煮剧,如何區(qū)分事件呢斥滤?很簡單:

startBtnClick$
      .pipe(
        switchMapTo(
          merge(
            intervalCanBeStopped$,
            resetBtnClick$
          )
        ),
        map(v => {
          if (typeof v === 'number') {
            return addOne
          } else {
            return reset
          }
        }),
        startWith({ count: 0 }),
        scan((acc, current) => current(acc))
      )

這樣做確實可以實現(xiàn)我們需要的效果,但勉盅,我們仔細想一想佑颇,事件流中的事件對我們的作用只是用來區(qū)分行為,那么我們是不是可以在原始流就把各自的事件轉換為各自的行為呢草娜?當然可以挑胸,我覺得這才是 Rx 編程模型想讓我們做的。

startBtnClick$
      .pipe(
        switchMapTo(
          merge(
            intervalCanBeStopped$.pipe(mapTo(addOne)),
            resetBtnClick$.pipe(mapTo(reset))
          )
        ),
        startWith({ count: 0 }),
        scan((acc, current) => current(acc))
      )

在合并兩個事件流之前宰闰,分別把兩個事件流中的事件轉換為了各自代表的行為茬贵,再合并為一個我們可以稱之為行為事件流的東西。完整程序代碼如下:

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

import { fromEvent, interval, merge } from "rxjs";
import { takeUntil, switchMapTo, scan, startWith, mapTo } from "rxjs/operators";

export default function App() {
  const pauseBtnRef = useRef(null);
  const startBtnRef = useRef(null);
  const resetBtnRef = useRef(null);
  const addOne = acc => ({ count: acc.count + 1 });
  const reset = acc => ({ count: 0 });

  useEffect(() => {
    const pauseBtnClick$ = fromEvent(pauseBtnRef.current, "click");
    const startBtnClick$ = fromEvent(startBtnRef.current, "click");
    const resetBtnClick$ = fromEvent(resetBtnRef.current, "click");
    const perSecond$ = interval(1000);
    const intervalCanBeStopped$ = perSecond$.pipe(takeUntil(pauseBtnClick$));
    const addOneOrReset$ = merge(
      intervalCanBeStopped$.pipe(mapTo(addOne)),
      resetBtnClick$.pipe(mapTo(reset))
    )

    const subscription = startBtnClick$
      .pipe(
        switchMapTo(
          addOneOrReset$
        ),
        startWith({ count: 0 }),
        scan((acc, current) => current(acc))
      )
      .subscribe(v => console.log(v));

    return () => {
      subscription.unsubscribe();
    };
  });

  return (
    <div className="App">
      <button ref={startBtnRef}>開始按鈕</button>
      <button ref={pauseBtnRef}>暫停按鈕</button>
      <button ref={resetBtnRef}>重置按鈕</button>
    </div>
  );
}

有任何問題移袍,請?zhí)砑游⑿殴娞枴白x一讀我”解藻。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市咐容,隨后出現(xiàn)的幾起案子舆逃,更是在濱河造成了極大的恐慌,老刑警劉巖戳粒,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件路狮,死亡現(xiàn)場離奇詭異,居然都是意外死亡蔚约,警方通過查閱死者的電腦和手機奄妨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苹祟,“玉大人砸抛,你說我怎么就攤上這事评雌。” “怎么了直焙?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵景东,是天一觀的道長。 經(jīng)常有香客問我奔誓,道長斤吐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任厨喂,我火速辦了婚禮和措,結果婚禮上,老公的妹妹穿的比我還像新娘蜕煌。我一直安慰自己派阱,他們只是感情好,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布斜纪。 她就那樣靜靜地躺著贫母,像睡著了一般。 火紅的嫁衣襯著肌膚如雪傀广。 梳的紋絲不亂的頭發(fā)上颁独,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機與錄音伪冰,去河邊找鬼誓酒。 笑死,一個胖子當著我的面吹牛贮聂,可吹牛的內(nèi)容都是我干的靠柑。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼吓懈,長吁一口氣:“原來是場噩夢啊……” “哼歼冰!你這毒婦竟也來了?” 一聲冷哼從身側響起耻警,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤隔嫡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后甘穿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腮恩,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年温兼,在試婚紗的時候發(fā)現(xiàn)自己被綠了秸滴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡募判,死狀恐怖荡含,靈堂內(nèi)的尸體忽然破棺而出咒唆,到底是詐尸還是另有隱情,我是刑警寧澤释液,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布全释,位于F島的核電站,受9級特大地震影響均澳,放射性物質發(fā)生泄漏恨溜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一找前、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧判族,春花似錦躺盛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辩撑,卻和暖如春界斜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背合冀。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工各薇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人君躺。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓峭判,卻偏偏與公主長得像,于是被迫代替她去往敵國和親棕叫。 傳聞我的和親對象是個殘疾皇子林螃,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350