一個外掛讓你在 Redux 中寫 promise 事半功倍

創宇前端發表於2018-01-31

Redux 的應用給我們開發帶來了很多便利,讓元件間互動不再那麼複雜,但 Redux 也有它的短板,我們知道要通過 Redux 改變一個 state 需要定義 actionType→action→reducer,這使得有時候一個很簡單的互動都需要寫一堆程式碼,我們今天要介紹的這個小外掛可以讓這個工作變得更加簡潔、優雅。

它就是:redux-promise-middleware,這是一個 Redux 的 middleware,引入專案的方式和其他的 redux middleware 一樣,具體可以檢視它的官方文件,這裡就不再贅述。

舉個典型的例子,通過 Redux 中發起一次登入請求,這個請求的 promise 會有3種狀態需要我們處理,分別是 pending、resolve、reject,在 pending 的時候頁面要顯示等待狀態,resolve 的時候要顯示請求成功,reject 的時候要提示使用者請求失敗,我們先通過對比來看一下用了這個元件之後程式碼是什麼樣子:

actionType.js

使用前:

export const LOGIN_PENDING = 'LOGIN_PENDING';
export const LOGIN_RESOLVE = 'LOGIN_RESOLVE';
export const LOGIN_REJECT = 'LOGIN_REJECT';
複製程式碼

使用後:

export const LOGIN = 'LOGIN';
複製程式碼

只需要寫一個?是的,讓我們往下看。

action.js

使用前:

import * as at from 'actionType';

export const login = (username, password) => async dispatch => {
  dispatch({ type: at.LOGIN_PENDING });
  try {
    const resp = await axios.post(URL_LOGIN, { username, password });
    dispatch({
      type: at.LOGIN_RESOLVE,
      result: resp
    });
  } catch(e) {
    dispatch({
      type: at.LOGIN_REJECT,
      message: e.message
    });
  }
}
複製程式碼

使用後:

import * as at from 'actionType';

export const login = (username, password) => ({
  type: at.LOGIN,
  payload: axios.post(URL_LOGIN, { username, password })
});
複製程式碼

寫完了?沒錯,就是這麼簡單。那等待和失敗的處理在哪裡呢?別急,我們們繼續往下看。

reducer.js

使用前:

import * as at from 'actionType';

export default (state, action) => {
  switch (action.type) {
    case at.LOGIN_PENDING:
      // 顯示等待圖示
    case at.LOGIN_RESOLVE:
      // 顯示登入成功
    case at.LOGIN_REJECT:
      // 顯示登入失敗
  }
}
複製程式碼

使用後:

import { PENDING, FULFILLED, REJECTED } from 'redux-promise-middleware';
import * as at from 'actionType';

export default (state, action) => {
  switch (action.type) {
    case `${at.LOGIN}_${PENDING}`:
      // 顯示等待圖示
    case `${at.LOGIN}_${FULFILLED}`:
      // 顯示登入成功
    case `${at.LOGIN}_${REJECTED}`:
      // 顯示登入失敗
  }
}
複製程式碼

正如你所看到的,用了這個 middleware 後,一個帶有 promise 的 action 會被包裝成三個狀態為字尾的 action 發給 reducer,並且此時 action.payload 中的值已經是 promise 執行後返回的結果,然後我們只需要拿 payload 的值分別操作 state 就行了。看到這裡你應該覺得,嗯,這個外掛確實能讓人少寫不少程式碼,但是反應快的朋友看了之後可能又有疑問了——在 action 裡面只返回一個 type 和 payload 的物件,這在處理一個請求的時候確實沒問題,那假如我在 login 之後緊接著又要 dispatch 另外一個 action 怎麼辦呢?

這個時候我們可以這樣寫:

import * as at from 'actionType';

export const login = (username, password) => dispatch => {
  dispatch({
    type: at.LOGIN,
    payload: axios.post(URL_LOGIN, { username, password })
  }).then(() => dispatch(afterLogin()));
}
複製程式碼

從筆者的使用經歷來看,以上的寫法基本能滿足絕大多數的場景,此時有的同學可能依然有疑問——這個 middleware 這麼擅自主張的就對 action 進行了改裝,會不會對專案的入侵太大?如果我的專案已經進行到一半,再引入它會不會讓我以前寫的 action 無法正常被 reducer 接收到?這個答案讓我們到它的原始碼中去尋找:

開啟它的倉庫地址,我們會發現它的程式碼很少,只有兩個檔案:index.jsisPromise.jsisPromise.js 定義了一個工具方法用來判斷一個物件是否是一個 promise,這裡就不再多講。主要的邏輯在 index.js 中,為了節約你的時間,我們用一個流程圖去大概描述它的邏輯:

一個外掛讓你在 Redux 中寫 promise 事半功倍

值得一提的是,因為它是一個 Redux 的 middleware,返回 意味著將 action 傳遞給下一個 middleware,直到傳遞到 reducer 中。從圖中可以看出,如果你不是按照它規定的格式書寫 action,它會直接透傳出去,因此它並不會對專案造成什麼副作用(除非你以前的 action 中的物件就是有叫 payload 的屬性,並通過 payload 直接把一個 promise 物件發給了 reducer,這種本身也是不規範的寫法)。

除了文章中講到的這些,redux-promise-middleware 還有許多其他的使用技巧幫助我們提高編碼效率,同時很多屬性也支援自定義,比如代表三種狀態的 action 的字尾等等,感興趣的同學可以下來自行閱讀官方文件。


關注微信公眾號:創宇前端(KnownsecFED),碼上獲取更多優質乾貨!

一個外掛讓你在 Redux 中寫 promise 事半功倍

相關文章