###### tags: `Angular` # 🥔來說說 RxJS 的使用方式 在開始分享如何使用 之前,先來簡單介紹 [RxJS](https://rxjs.dev/) 是什麼,它可以幫助我們解決什麼問題,以及它有什麼優點讓我們值得學習。 ## 簡單介紹 首先,RxJS 是一套 Javascript 的函式庫,主要是用來處理非同步、事件的程式問題: * 非同步: AJAX / Service Work / Promise * 事件: 各式 DOM 事件 (click, keyup ...) ![](https://i.imgur.com/2DY58G2.png) 非同步資料的概念就是,開發者不能確定在一`時間序上的串流資料事件`上,何時會拿到資料,因此會先寫好相對印的程式邏輯,等待事件發生時執行相對應的程式就好,因此我們可能會遇到的情況,`在收到非同步的回傳資料後,要再發送一個請求`。 通常在這種情境下,很容易直覺的就寫出類似下方的情形, ```typescript= doA() { doB() { doC() { } } } ``` > 當 Callback 被一層層嵌套,導致程式碼難以閱讀和維護的情況,就被稱作 Callback Hell。 那為了避免上面的情形,我們可以學習如何適當的使用 RxJS 來幫助我們做資料的存取。 當然,除了 RxJS 也有其他方式可以幫助我們達到讓程式去巢狀的情形 (Promise, async/await...) ### 關於 Reactive Extensions (Rx) Reactive Extensions 簡稱 ReactiveX 一般簡寫為 Rx , [ReactiveX](https://reactivex.io/) 網站會發現,他有明確的定義 ReactiveX 本質上為一種設計理念,主要由三個核心概念組成 : > ReactiveX is a combination of the best ideas from the **Observer pattern**, the **Iterator pattern**, and **functional programming**. 1. Observer pattern 2. Iterator pattern 3. functional programming 所以只要有任何的程式語言去實現 Rx ,那 Rx 的概念在任何平台上都有可能出現的。 ## 如何使用 [RxJS](https://rxjs.dev/)、[RxJs Marbles](https://rxmarbles.com/)、[Reactive How](https://reactive.how/) 開始使用之前,我們先來認識會使用到的主要東西: * Observable 可觀察物件 - 未來將會發生的值/事件,並且可以被觀察的。 * Observer 觀察者物件 - 知道如何收到 Observable 回傳的值。 * Subscription 訂閱物件 - 被 Observer 觀察的 Observable,主要用來取消訂閱。 * Operators 操作符 - 純函式(function) - 處理事件資料 * Subject 主體物件 - 類似於 EventEmitter - 唯一能做到將廣播收到的事件資料給多位 Observer ### 先來寫段小程式 > 題目: 請將陣列內的非偶數數字乘以十後加總 > 輸入: [1,2,3,4] 第一種最常見的寫法就是直觀的用 `for + if` 寫出來: ==for+if== ```typescript= const arr = [1,2,3,4]; let sum = 0; for (let i = 0; i < arr.length; i++) { if (i%2 == 1) { sum += i*10; } } ``` 第二種,接觸過原型(prototype)方法(method)的人 可能會選擇使用 functional programming 的方式來寫: ==functional programming== ```typescript= const arr = [1,2,3,4]; let sum = arr .filter((a) => a%2==1) .map((a) => a*10) .reduce((a,b) => a+b); ``` 這邊我們就不仔細比較兩種寫法的差異,我們直接來看第二種的寫法,***將原資料經轉換後,變成想要的資料***,這樣說可能很抽象,所以我畫了一張**彈珠圖**。 > 彈珠圖是在表示 RxJS 的資料流時十分常見的示意圖,相關網站可以參考 [RxJS Marbles](https://rxmarbles.com/)。 ![彈珠圖](https://i.imgur.com/c0282ZM.png) 那這樣我們來試著把它改成 RxJS 的寫法: ![](https://i.imgur.com/JqanLMC.png) ==RxJS== ```typescript= concat([1,2,3,4]).pipe( filter(a => a%2==1), map(x => x*10), reduce((t,v) => t+v), ).subscribe(); ``` ### 完整程式碼 ```typescript= import { Component, OnInit } from '@angular/core'; import { concat, Observable, of } from 'rxjs'; import { delay, filter, map, reduce, tap } from 'rxjs/operators'; @Component({ selector: 'app-rxjs', templateUrl: './rxjs.component.html', styleUrls: ['./rxjs.component.scss'] }) export class RxjsComponent implements OnInit { constructor() { } ngOnInit(): void { console.log('demoFucByN', this.demoFucByN()); console.log('demoFucByFP', this.demoFucByFP()); this.demoFucByRxJS().subscribe((res) => console.log('demoFucByRxJS', res)); } // demo demoFucByN() { const arr = [1,2,3,4]; let sum = 0; for (let i = 0; i < arr.length; i++) { if (i%2 == 1) { sum += i*10; } } return sum; } demoFucByFP() { const arr = [1,2,3,4]; let sum = arr .filter((a) => a%2==1) .map((a) => a*10) .reduce((a,b) => a+b); return sum; } demoFucByRxJS() { return this.callApi$().pipe( filter(a => a%2==1), map(x => x*10), reduce((t,v) => t+v), ) } callApi$():Observable<any> { const arr = [1,2,3,4] return concat(arr).pipe(delay(1000)); } } ``` ### RxJS 相關參考資料 [Netflix JavaScript Talks - RxJS Version 5](https://www.youtube.com/watch?v=COviCoUtwx4) [30 天精通 RxJS](https://ithelp.ithome.com.tw/users/20103367/ironman/1199) [[RxJS] 轉換類型 Operators (1) - map / scan / pairwise](https://fullstackladder.dev/blog/2020/10/03/mastering-rxjs-18-map-scan-pairwise/) [[RxJS] 轉換類型 Operators (2) - switchMap / concatMap / mergeMap / exhaustMap](https://fullstackladder.dev/blog/2020/10/04/mastering-rxjs-19-switchmap-concatmap-mergemap-exhaustmap/)