為什麼我應該學習RxJs?
前言
只要學習到Angular、Nest.Js,就一定會聽過RxJs ── 這個令人又愛又恨的套件
恨的是,框架本身就夠多東西要學了,還多一個RxJs來插花
而且這還不是個簡單的小套件!
光是學會使用RxJs,可能就需要近1個月專心學習、使用後,才能轉換思路、掌握他
愛的是,當你熟悉他之後,你會發現他有多麼強大、好用
原本需要再安裝lodash等util類輔助套件來做一些商業邏輯的資料整理、計算
但透過RxJs竟然可以很輕易的做好資料分組、運算,組合出想要的資料集,連額外輔助套件都不必安裝了!
而做為Node.Js界裡響應式開發(Reactive Programming)的代表RxJs
除了已內置在主流的後端Nest.Js、(相對沒那麼有人氣)前端Angular之外
在任何的專案裡,皆可以視需求融入使用
學會了RxJs,更是學會了響應式開發(Reactive Programming)
不論在何種語言,都有機會使用到相關觀念
簡單敘述響應式開發(Reactive Programming)
等等,學個RxJs,怎麼又多了個新名詞?!
響應式開發(Reactive Programming),簡稱ReactiveX,縮寫Rx
RxJs就是響應式開發在JavaScript裡的實現
以此類推,其他語言也有RxJava、RxGO、RxPy….
相比於我們所熟悉的指令式且帶狀態的開發習慣
響應式開發則是更高階的抽象
把指令式的程式流,轉換成事件流
--a---b-c---d---X---|->
`a, b, c, d` => 事件觸發
`X` => 遇到錯誤,即exception、error
`|` => 事件流完全結束,即completed
`--->` => 時間流
每一次的事件觸發(即上圖的a, b, c, d),都會發送出資料
再將資料丟進水管(pipe)裡,每經過一條管路,就做一次運算或整理,最後回傳結果
範例
而所謂的水管
若熟悉java,就是java 8 裡的stream功能
而在JavaScript裡,就是陣列裡會出現的寫法(又稱functional programming)
const ARRAY_DATA = [1, 2, 3, 4, 5];
// 一般的指令式寫法
const result = [];
for (const num of ARRAY_DATA) {
const temp = num * 2;
if (temp < 5) {
result.push(temp);
}
}
console.log(result); // [2, 4]
// functional programming 寫法
result = ARRAY_DATA
.map(num => num * 2) // [2, 4, 6, 8, 10]
.filter(num => num < 5); // [2, 4]
console.log(result) // 輸出 [2, 4]
光是語言原生提供的functional programming寫法就明顯感受到程式碼精簡、易讀性
而RxJs的水管(pipe)更加強大,可以同時處理同步、非同步函式
在前端使用,甚至還可以直接監聽click等相關事件!
可以直接想成functional programming寫法,不在侷限於陣列資料,而是任何事件!
再由各種小邏輯函式組裝而成工廠流水線
以上述例子來看,若用RxJs書寫,則程式碼看起來像這個樣子
from([1, 2, 3, 4, 5]).pipe( // from,把陣列資料逐一送出
map(num => num * 2), // 1 * 2 = 2,繼續往後送出資料
filter(num => num < 5), // 2 < 5,,符合條件,繼續往後。當num >= 5時,不符條件,不後送
toArray(), // 當所有資料都處理完成後,組裝回陣列
).subscribe(console.log); // 輸出結果 [2, 4]
當然因為這例子直接用原生語法最簡單
並沒有體現出RxJs的強大之處,硬要使用RxJs則略顯冗長
旨在說明「指令式的程式流」 -> 「資料工廠流水線」的概念
可以想像更為複雜的業務情景:
工廠流水線,不在是簡單暱名函式(工人)足以負擔
其商業邏輯冗長,拆數段後封裝在許多子function裡(更多的工人!)
甚至有些是非同步函式向其他api取資料回來後才能繼續計算(比較高階的工人)
其帶來的管道寫法有多麼優雅、易讀!
可見後面例子
一個使用RxJs產報表的例子
假設有一報表需求,需經過一連串的整理、彙總後再回傳資料給前端
只需要公開genSaleReport()
其餘複雜邏輯則語意化命名藏在底下(以下範例故意將private function命名成大寫便於閱讀)
當有bug需要追查程式碼時,亦可以從入口處迅速理解這張報表的資料流大至上長什麼樣子
再往細部function深追問題
每個private function亦可視需要補上註解(假設無法很精確的用函式命名表達功能)
當滑鼠懸浮在function上時,可在IDE更直白的理解函式功能,加速問題處理!
export class SaleReportService {
private logger = new Logger();
genSaleReport(DATA: any[]) {
return from(DATA).pipe( // 可能由DB、其他系統API取得資料
map(datum => this.ORGANIZE_DATA(datum)), // 同步函式,將資料整理成一漂亮的object
tap(datum => this.logger.log(`saleReport-整理成object: ${datum}`)), // LOG出組裝後的資料
map(async datum => await this.ASYNC_CALC_DATA(datum)), // 一些複雜的計算,甚至須需用非同步函式,封裝在`ASYNC_CALC_DATA`
tap(datum => this.logger.log(`saleReport-複雜計算後的結果: ${datum}`)), // LOG出計算後的資料
map(datum => this.ORGANIZE_FINAL_DATA(`saleReport-最終資料: ${datum}`)), // 同步函式2,組裝出最終版的資料結構
filter(datum => datum.price > 1000), // 上線運作一段時間後,應主管要求,只須呈現 price > 1000 的資料。簡單的再加上filter,不必費力細追邏輯後再決定該加在何處較適當
toArray(), // 當所有資料都處理完成後,組裝成陣列,一次回傳
);
}
/**
* step 1: 整理資料成一漂亮的object
*/
private ORGANIZE_DATA(datum) { ... }
/**
* step 2: 複雜的業務邏輯,還有非同步運算
*/
private async ASYNC_CALC_DATA(datum) { ... }
/**
* step 3: 都算完了!將資料做最後梳理,回傳完整資料集
*/
private ORGANIZE_FINAL_DATA(datum) { ... }
}
由上述片段程式碼可以發現
不論註解書寫情況是下列哪種
- 寫在function上方
- 直接寫在每一運算子後面
- 不寫,function命名夠語意化可以直接理解函式功能
都可以讓維護者很迅速的進入資料流,使得程式碼更易於維護!
我是因應框架(Angular 或 Nest.Js)才來學RxJs,應該學到什麼程度?
Angular
前端使用,只需要熟悉他的觀察者模式Observer、Observable
管道指令裡知道from、of、filter等幾個基本語法就足夠使用了
因為複雜的商業邏輯在後端處理完成了,前端只會拿到整理好的結果並著重在畫面渲染
比較重要的是須熟悉Subject的使用方式。因Component層層嵌套下,不可能靠深層傳遞資料來觸發變化畫面內容
這樣的程式碼相當難維護,就連原作者自己可能都維護不下去…
應透過RxJs的Subject以很輕鬆、有效的方式跨越層層Component,依據資料觸發直接變化相關畫面
在單純的Http打後端API,Angular則是框架直接封裝了RxJs寫法,因此在各式教學文都可以看到下列寫法
this.http.get('<YOUR_DOMAIN>/api/user')
.subscribe(res => this.data = res);
透過RxJs須訂閱才會觸發動作的特性,更有效的結合了Angular 依賴注入,將API抽象至Service裡
只有當頁面需要使用時,再觸發打API取資料,達到更佳的邏輯分離,讓程式碼更具結構化
Nest.Js
就算完全不會RxJs,也不影響你學習、開發Nest.Js!
可以把RxJs當作是錦上添花的工具,熟悉他,可以把商業邏輯封裝的更加漂亮(如前述報表例子)
不熟悉他,頂多是死背Interceptor等框架提供的寫法
反正底層會自動處理好相關事務,您只需要知道該怎麼墊邏輯即可
而商業邏輯使用熟悉的指令式寫法或Promise也不會影響需求開發
但仍建議好好掌握RxJs!
因為在複雜的商業邏輯下,RxJs可以很漂亮地像工廠流水線般處理同步、非同步函式
較不會看到第一個例子的指令式寫法,塞滿各種for, if來整理資料
亦助於更深層理解框架底層的運作方式
複雜的商業邏輯,就會使用到相當多的Operator
尤其pairwise(), groupBy(), toArray(), partition() …等
更著重在熟悉各種Operator的應用
所幸常用的就那幾個Operator,其他則有需要時再查閱官方文件就好
在Subject的應用就相當的少,甚至整個專案都用不到
也就更加要求您熟悉響應式開發,轉換大腦寫程式的思路
相比於前端,在起步時比較痛苦
熟悉後,則會感受到程式碼更加的原子化(更易於寫test case)、封裝性、可讀性,帶來的優點相當多!
推薦資料
寫的非常清晰,尤其在開發思路的轉換,很明確的提供例子感受差異
很可惜演講時仍是 RxJs 5 的版本
隔年的6版就是重大的Breaking change,全面改成pipe的管道寫法
對於 Angular ,帶來的最大優點就是搖樹優化可以把未使用的運算子搖掉,大幅瘦身前端打包後的結果
但仍值得觀看,用淺顯易懂的例子講解,有助於更快速理解RxJs帶來的優勢!