RxJS 筆記

簡單紀錄一下寫 RxJS 時踩到的雷。

新手常常搞錯的觀念:map 跟 mergeMap 的差別

map 是用來做轉換資料

1
2
3
fromEvent(window, 'click')
.map((e) => e.target) // 從 event 物件轉換 dom 元素
.subscribe((target) => console.log(target))

mergeMap 是用來壓平 Observable,代表在 mergeMap回傳的東西一定是一個 Observable,而不是一般的資料型態。

先來看錯誤的寫法:

1
2
3
4
const btn = document.querySelector('.btn')
fromEvent(btn, 'click')
.mergeMap((e) => e.target)
.subscribe((target) => console.log(target)) // 不會被執行到

會寫出這樣的東西通常是因為你以為這個跟 Array.flat 的用法一樣,就算我傳進去的值不是一個巢狀的 Array,我還是可以拿到結果:

1
2
const array = [1, 2, 3]
console.log(array.flat()) // [1, 2, 3]

但是 mergeMap 的概念不是這樣,它只會預期你回傳一個 Observable(或者是 iterable),所以如果你回傳的東西不對的話就會噴一個 InvalidObservableTypeError 的錯誤。

正確的作法應該是搭配 of 來把值轉換成一個 Observable

1
2
3
fromEvent(btn, 'click')
.pipe(mergeMap((e) => of(e.target))) // 把 e.target 轉換成 Observable
.subscribe((value) => console.log(value)) // <button class="btn">Click Me</button>

因為這裡有先用 ofe.target 轉換成 Observable 以後再回傳,所以 mergeMap 就會把 Observable 壓平,拿到 Observable 裡面的值(這邊是 button 的 DOM 元素),再丟給 subscribe,印出 button 元素。

mergeMapconcatMapswitchMap 這幾種 Map 都是同樣的原理,只要你用了它們,就永遠要記得回傳一個 Observable 給它。

常見的應用場景:Redux observable

這段只是做個補充,我當初就是在寫 Redux observable 的時候就是因為分不清楚 mapswitchMap 的實際差別,所以才會有點看不懂自己到底在寫什麼。

下面是一個簡單的 Epic:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class API {
static async getPosts(): Promise<Post[]> {
const response = await fetch('https://jsonplaceholder.typicode.com/posts')
const data = await response.json()
return data
}
}

export const getPostsEpic: Epic = ($action) =>
$action.pipe(
ofType(getPosts().type),
switchMap(() => from(API.getPosts())),
map((response) => getPostsResult(response))
)

這個 Epic 是在接收到對應的 action 時去呼叫 API,成功以後再發出一個新的 action 到 reducer。

不過這樣子寫有一些問題:

  1. switchMap 本身也是一種 map,所以可以直接在裡面決定要回傳什麼 action 就好了
  2. 現在的寫法沒辦法做錯誤處理

比較正規的作法是這樣子:

1
2
3
4
5
6
7
8
export const getPostsEpic: Epic = ($action) =>
$action.pipe(
ofType(getPosts().type),
switchMap(() => from(API.getPosts()).pipe(
map((response) => getPostsResult(response)),
catchError((error) => of(getPostsFailed(error.message))
))
)

第一個是我們把要回傳 action 的邏輯都搬移到 from(API.getPosts()) 這個 Observable 底下處理,如果 API 沒有問題的話就用 map 回傳 getPostsResult,否則就在 catchError 內回傳 getPostsFailed

第二個是要注意 catchError 的回傳值必須是一個 Observable,所以才需要用 of 來包起來。

Redux Observable 懶人包
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×