RxJS 是什麼?!(2) 被觀察的世界的建立與轉換

Ken
8 min readMay 16, 2021

--

從每隻螞蟻背上的糖,到小明作業本上的加總~Operator

Photo by Rachel on Unsplash

上一篇提及了一個架空的螞蟻世界是如何被建立的,每隻螞蟻經過時,都會通知小明自己的收穫,小明已經看到了他們背上的方糖了,但距離小明完成暑假作業還有一小段路。

接著我們將這個螞蟻世界做點轉換,也就是我們聘請一個盤點人員,負責紀錄食物庫存數量,建立螞蟻盤點員的世界。如此,每隻螞蟻進庫存時,螞蟻盤點員都會盤點紀錄、加總並通知觀察者,當前庫存量。

因此小明就可以直接從觀察螞蟻世界轉換為螞蟻盤點員的世界。

假設我們這次觀察到螞蟻們所背負的食物如下,前 5 隻螞蟻都各背 1 顆糖果,第6隻背 0 顆糖,我們可以嘗試用以下的圖表示此架空的螞蟻世界

接下來,因為我們有聘請了盤點員sumUp及建立一個倉庫,所以我們將精力轉換為觀察螞蟻盤點員所建立的世界,盤點員的任務就是將每隻經過的螞蟻,身上背負的食物,加總起來,紀錄庫存

負責世界的轉換與建立的工人們— Operator

就像是數學運算有加減乘除等等這些 Operator 運用;RxJS 也提供我們一些Operator,來建立一個可被觀察的世界(Observable),或將一個可被觀察的世界(Observable),轉換成另外一個可被觀察的世界(Observable)

而上圖這個 sumUp 就是一位工人,負責將 螞蟻世界 轉換為 螞蟻盤點員的世界, sumUp 這位工人,在 RxJS 裡就叫做 scan operator,我們必須告訴盤點員 scan,如何盤點、以及倉庫現在庫存量是多少,scan 便可以依照小明訓練他的累加方式計算當前庫存,將螞蟻世界轉換為螞蟻盤點員的世界(將某個 Observable 的每個值,進行累加,轉換成為一個新的 Observable ,通知訂閱者累加後的結果!)這個世界會把每次加總的結果通知小明。

scan 就像是 array reduce 一樣,需要傳入 2 個參數,一個是如何累加的 function、另一個是新倉庫的庫存— 0(初始值)。

接著,我們可以透過 螞蟻盤點員將 螞蟻世界 轉換為 螞蟻盤點員的世界(即我們透過 scan 將 ants$ 轉換為 inventoryClerk$ ),小明只要從觀察 ants$ 改成 觀察 inventoryClerk$ 就可以看到盤點員每次加總後的結果,快要可以交暑假作業囉~~

這裡使用到 Observable 提供的 method pipe ,pipe 在 Functional Programming 中很重要的概念,它的意義是將一連串不同的操作組合起來。有些時候,我們不只有想對 Observable做一次轉換,而是想要做多次轉換。

就好比 Array 可以進行 array method dot chaining , Observable 也可以透過 pipe 將多個操作組合起來,最後轉換為新的 Observable。

對 pipe 的概念如果有興趣,可以參考我之前分享的Slides,或是未來有機會 還可以再寫一篇?

題外話,RxJS 初期也是採用 dot chaining,所以可以看到很多像 array method 一樣的 chaining ,這裡參考胡立大大文章內的程式碼

some RxJS code | credit:希望是最淺顯易懂的 RxJS 教學

盤點員的世界 — 累加紀錄

我們可以用以下的 彈珠圖 (Marble Diagram),表示從螞蟻世界 轉換成 觀察螞蟻盤點員的世界(也就是一個 observable 到另一個 observable的轉換)。至於這個世界是怎麼從 A 轉換到 B 的?

  1. 要聘請適合的工人 → 使用符合需求的 operator(這裡我們聘請盤點員 scan 來協助加總盤點)
  2. 告訴你的工人:新世界的轉換要怎麼施工 → 了解盤點員的工作:庫存是怎麼被紀錄的 (這裡也就是程式開發者須依據商業邏輯設計的部分)
  3. 小明要知道改成訂閱成盤點員世界

此時,改訂閱盤點員所在的世界,我們便能得知現在庫存的數量。

codesandbox demo

更精準地完成暑假作業 — 知道最後多少即可

懶、還要更懶,現在我們的盤點員非常勤勞,每次更新庫存都會報告小明

但現在,身為客戶的小明,啊不是~是身為人類的小明,還是覺得有點困擾…,已經知道螞蟻有幾隻了( 見前半部的假設,我們預設螞蟻世界只有6隻螞蟻 — 架空螞蟻世界是有限集合的值),小明只要知道最後一隻螞蟻進去時的總庫存是多少即可,不要每次進貨都跟小明報告呀~~~。

小明:你可以最後再通知我嗎?螞蟻世界創辦人:行!沒問題~。

創辦人苦思許久,一時間想不到好辦法,來滿足小明這個客戶。忽然一個靈光乍現,除了聘請盤點員,可以再增聘一個對外的客戶關係聯繫,只有當盤點紀錄完總庫存,再委由這個客戶關係人去跟小明報告即可。當即決定聘請工人 last。

客戶關係人的世界 — 回覆最後一個

優秀的工人 last,為了滿足小明的需求,會一直聽著盤點員的報告,直到最後一次,他會把最後一個結果建立自己的世界。將盤點員最後報告的庫存紀錄通知小明。last 只要知道到上一個 Observable 結束,就會將最後一個值建立新的Observable 通知訂閱者。

至於什麼是結束?結束的定義是什麼?上一篇我們知道其中一個,只要Observable 執行 complete() 這個方法,就代表一個 Observable 結束了。聰明的 last 還有其他辨識結束的方法,不過我們可以有機會再聊。

建立 customRelation$ 我們可以將 inventoryClerk$ 和 last 串起來(pipe):

也可以將原本的 ant$ 和 scan, last 串起來 (pipe):

這裡我們便可以見識到 pipe 的力量,可以將多餘的變數命名省略,直接一步到位將最後所需要的操作全串接起來。

ya~ 透過 螞蟻世界、盤點員、客戶關係的協力,我們完成了小明的暑假作業囉。

過了一段時間,螞蟻世界的創辦人的人脈擴增、又認識更多朋友,此時他發現 reduce 的工作能力,非常符合小明的需求,同時具備有 scan 跟 last 的能力。

於是你知道的…,我們便跟 scan 還有 last 說掰掰了~

codesandbox demo

小結

  1. Operator 可以創造世界也可以轉換世界,是一群可靠的工人們
  2. 隨著你認識的 Operator 越多,就越能找到適合的 Operator
  3. 有一些工人需要根據你的需求,來建立什麼樣的世界訓練,告訴你的 Operator 你想要做什麼(比如 reduce, scan, filter, map 等)
  4. 我們目前認識 scan, last, reduce

Transformation Operators — scan

Filtering Operators — last

Mathematical and Aggregate Operators — reduce

為了有機會的話,將介紹更多 RxJS 的 operators。

參考資料

RxJS 官方文件 Operators
RxJS Marbles
希望是最淺顯易懂的 RxJS 教學

如果這篇文章對你有幫助,請幫我拍手一下
你的拍手及分享是對我最大鼓勵與書寫下篇文章的動力

1下 閱。
5–10下 你認為這篇文章還不錯。
11–20下 你認為這篇貼文對你很有幫助。
21–40下 你非常喜歡這篇貼文,覺得實用。
40–50下 希望我可以多分享關於 Rx 相關的文章

--

--