Redux 入門教程(2):中介軟體與非同步操作

發表於2016-09-20

上一篇文章,我介紹了 Redux 的基本做法:使用者發出 Action,Reducer 函式算出新的 State,View 重新渲染。

bg2016092001

但是,一個關鍵問題沒有解決:非同步操作怎麼辦?Action 發出以後,Reducer 立即算出 State,這叫做同步;Action 發出以後,過一段時間再執行 Reducer,這就是非同步。

怎麼才能 Reducer 在非同步操作結束後自動執行呢?這就要用到新的工具:中介軟體(middleware)。

bg2016092002

一、中介軟體的概念

為了理解中介軟體,讓我們站在框架作者的角度思考問題:如果要新增功能,你會在哪個環節新增?

(1)Reducer:純函式,只承擔計算 State 的功能,不合適承擔其他功能,也承擔不了,因為理論上,純函式不能進行讀寫操作。

(2)View:與 State 一一對應,可以看作 State 的視覺層,也不合適承擔其他功能。

(3)Action:存放資料的物件,即訊息的載體,只能被別人操作,自己不能進行任何操作。

想來想去,只有傳送 Action 的這個步驟,即store.dispatch()方法,可以新增功能。舉例來說,要新增日誌功能,把 Action 和 State 列印出來,可以對store.dispatch進行如下改造。

上面程式碼中,對store.dispatch進行了重定義,在傳送 Action 前後新增了列印功能。這就是中介軟體的雛形。

中介軟體就是一個函式,對store.dispatch方法進行了改造,在發出 Action 和執行 Reducer 這兩步之間,新增了其他功能。

二、中介軟體的用法

本教程不涉及如何編寫中介軟體,因為常用的中介軟體都有現成的,只要引用別人寫好的模組即可。比如,上一節的日誌中介軟體,就有現成的redux-logger模組。這裡只介紹怎麼使用中介軟體。

上面程式碼中,redux-logger提供一個生成器createLogger,可以生成日誌中介軟體logger。然後,將它放在applyMiddleware方法之中,傳入createStore方法,就完成了store.dispatch()的功能增強。

這裡有兩點需要注意:

(1)createStore方法可以接受整個應用的初始狀態作為引數,那樣的話,applyMiddleware就是第三個引數了。

(2)中介軟體的次序有講究。

上面程式碼中,applyMiddleware方法的三個引數,就是三個中介軟體。有的中介軟體有次序要求,使用前要查一下文件。比如,logger就一定要放在最後,否則輸出結果會不正確。

三、applyMiddlewares()

看到這裡,你可能會問,applyMiddlewares這個方法到底是幹什麼的?

它是 Redux 的原生方法,作用是將所有中介軟體組成一個陣列,依次執行。下面是它的原始碼。

上面程式碼中,所有中介軟體被放進了一個陣列chain,然後巢狀執行,最後執行store.dispatch。可以看到,中介軟體內部(middlewareAPI)可以拿到getStatedispatch這兩個方法。

四、非同步操作的基本思路

理解了中介軟體以後,就可以處理非同步操作了。

同步操作只要發出一種 Action 即可,非同步操作的差別是它要發出三種 Action。

  • 操作發起時的 Action
  • 操作成功時的 Action
  • 操作失敗時的 Action

以向伺服器取出資料為例,三種 Action 可以有兩種不同的寫法。

除了 Action 種類不同,非同步操作的 State 也要進行改造,反映不同的操作狀態。下面是 State 的一個例子。

上面程式碼中,State 的屬性isFetching表示是否在抓取資料。didInvalidate表示資料是否過時,lastUpdated表示上一次更新時間。

現在,整個非同步操作的思路就很清楚了。

  • 操作開始時,送出一個 Action,觸發 State 更新為”正在操作”狀態,View 重新渲染
  • 操作結束後,再送出一個 Action,觸發 State 更新為”操作結束”狀態,View 再一次重新渲染

五、redux-thunk 中介軟體

非同步操作至少要送出兩個 Action:使用者觸發第一個 Action,這個跟同步操作一樣,沒有問題;如何才能在操作結束時,系統自動送出第二個 Action 呢?

奧秒就在 Action Creator 之中。

上面程式碼是一個非同步元件的例子。載入成功後(componentDidMount方法),它送出了(dispatch方法)一個 Action,向伺服器要求資料 fetchPosts(selectedSubreddit)。這裡的fetchPosts就是 Action Creator。

下面就是fetchPosts的程式碼,關鍵之處就在裡面。

bg2016092003

上面程式碼中,fetchPosts是一個Action Creator(動作生成器),返回一個函式。這個函式執行後,先發出一個Action(requestPosts(postTitle)),然後進行非同步操作。拿到結果後,先將結果轉成 JSON 格式,然後再發出一個 Action( receivePosts(postTitle, json))。

上面程式碼中,有幾個地方需要注意。

(1)fetchPosts返回了一個函式,而普通的 Action Creator 預設返回一個物件。

(2)返回的函式的引數是dispatchgetState這兩個 Redux 方法,普通的 Action Creator 的引數是 Action 的內容。

(3)在返回的函式之中,先發出一個 Action(requestPosts(postTitle)),表示操作開始。

(4)非同步操作結束之後,再發出一個 Action(receivePosts(postTitle, json)),表示操作結束。

這樣的處理,就解決了自動傳送第二個 Action 的問題。但是,又帶來了一個新的問題,Action 是由store.dispatch方法傳送的。而store.dispatch方法正常情況下,引數只能是物件,不能是函式。

這時,就要使用中介軟體redux-thunk

上面程式碼使用redux-thunk中介軟體,改造store.dispatch,使得後者可以接受函式作為引數。

因此,非同步操作的第一種解決方案就是,寫出一個返回函式的 Action Creator,然後使用redux-thunk中介軟體改造store.dispatch

六、redux-promise 中介軟體

既然 Action Creator 可以返回函式,當然也可以返回其他值。另一種非同步操作的解決方案,就是讓 Action Creator 返回一個 Promise 物件。

這就需要使用redux-promise中介軟體。

這個中介軟體使得store.dispatch方法可以接受 Promise 物件作為引數。這時,Action Creator 有兩種寫法。寫法一,返回值是一個 Promise 物件。

寫法二,Action 物件的payload屬性是一個 Promise 物件。這需要從redux-actions模組引入createAction方法,並且寫法也要變成下面這樣。

上面程式碼中,第二個dispatch方法發出的是非同步 Action,只有等到操作結束,這個 Action 才會實際發出。注意,createAction的第二個引數必須是一個 Promise 物件。

看一下redux-promise原始碼,就會明白它內部是怎麼操作的。

從上面程式碼可以看出,如果 Action 本身是一個 Promise,它 resolve 以後的值應該是一個 Action 物件,會被dispatch方法送出(action.then(dispatch)),但 reject 以後不會有任何動作;如果 Action 物件的payload屬性是一個 Promise 物件,那麼無論 resolve 和 reject,dispatch方法都會發出 Action。

中介軟體和非同步操作,就介紹到這裡。下一篇文章將是最後一部分,介紹如何使用react-redux這個庫。

相關文章