簡單紀錄一下寫 RxJS 時踩到的雷。
新手常常搞錯的觀念:map 跟 mergeMap 的差別
map 是用來做轉換資料:
1 | fromEvent(window, 'click') |
mergeMap 是用來壓平 Observable,代表在 mergeMap 內回傳的東西一定是一個 Observable,而不是一般的資料型態。
先來看錯誤的寫法:
1 | const btn = document.querySelector('.btn') |
會寫出這樣的東西通常是因為你以為這個跟 Array.flat 的用法一樣,就算我傳進去的值不是一個巢狀的 Array,我還是可以拿到結果:
1 | const array = [1, 2, 3] |
但是 mergeMap 的概念不是這樣,它只會預期你回傳一個 Observable(或者是 iterable),所以如果你回傳的東西不對的話就會噴一個 InvalidObservableTypeError 的錯誤。
正確的作法應該是搭配 of 來把值轉換成一個 Observable:
1 | fromEvent(btn, 'click') |
因為這裡有先用 of 把 e.target 轉換成 Observable 以後再回傳,所以 mergeMap 就會把 Observable 壓平,拿到 Observable 裡面的值(這邊是 button 的 DOM 元素),再丟給 subscribe,印出 button 元素。
mergeMap、concatMap、switchMap 這幾種 Map 都是同樣的原理,只要你用了它們,就永遠要記得回傳一個 Observable 給它。
常見的應用場景:Redux observable
這段只是做個補充,我當初就是在寫 Redux observable 的時候就是因為分不清楚 map 跟 switchMap 的實際差別,所以才會有點看不懂自己到底在寫什麼。
下面是一個簡單的 Epic:
1 | class API { |
這個 Epic 是在接收到對應的 action 時去呼叫 API,成功以後再發出一個新的 action 到 reducer。
不過這樣子寫有一些問題:
switchMap本身也是一種 map,所以可以直接在裡面決定要回傳什麼 action 就好了- 現在的寫法沒辦法做錯誤處理
比較正規的作法是這樣子:
1 | export const getPostsEpic: Epic = ($action) => |
第一個是我們把要回傳 action 的邏輯都搬移到 from(API.getPosts()) 這個 Observable 底下處理,如果 API 沒有問題的話就用 map 回傳 getPostsResult,否則就在 catchError 內回傳 getPostsFailed。
第二個是要注意 catchError 的回傳值必須是一個 Observable,所以才需要用 of 來包起來。