redux-promise-utils
What
redux-promise-utils 是一個基於 redux-thunk 和 redux-actions 的工具,符合 FSA 規范,方便開發者處理異步 Action,減少大量冗余的 template 代碼。
Why
redux 一開始的設計就是用於處理同步的 Action。通過調用 Action 函數后 dispatch 返回的 type 和 payload ,最終交由 reducer 處理。reducer 匹配到相應的 type 並進行處理。
說到 redux 處理異步 Action,最為人熟知的就是 redux-thunk。redux-thunk 是一個 middleware,它可以在 reducer 延遲處理 Action,並在異步的相應回調中再 dispatch Action。所以我們可以認為 redux-thunk 其實不是專門處理異步用的 middleware,而是可能會延遲執行 dispatch 的函數。
常規 redux-thunk
在大多數的 redux-thunk 規范中,對一個請求需要定義三種Action,發起請求前,請求成功后,請求失敗后。一般代碼都是如下所示。
// constant/actionType.js
export default {
FETCH: 'FETCH_LIST_START',
FETCH_SUCCESSED: 'FETCH_LIST_SUCCESSED',
FETCH_FAILED: 'FETCH_LIST_FAILED'
}
// data/action.js
import ACTION_TYPE from '../constant/actionType'
import * as api from './api'
// start fetch
export function fetchAction(options) {
return dispatch => {
dispatch({
type: ACTION_TYPE.FETCH_LIST,
payload: options
})
return api.fetchList(options)
.then((response) => {
dispatch({
type: ACTION_TYPE.FETCH_LIST_SUCCESSED,
payload: {
options,
response
}
})
})
.catch((response) => {
dispatch({
type: ACTION_TYPE.FETCH_LIST_FAILED,
payload: {
options,
response
}
})
})
}
}
// data/reducer.js
import ACTION_TYPE from '../constant/actionType'
const initState = {}
export function reducer(state = initState, action) {
switch(action.type) {
case ACTION_TYPE.FETCH_LIST:
// your handler
return state
case ACTION_TYPE.FETCH_LIST_SUCCESSED:
// your handler
return state
case ACTION_TYPE.FETCH_LIST_FAILED:
// your handler
return state
default:
return state
}
}
一個簡單的請求 Action 需要如此多的 template 代碼。一旦業務量起來后,幾十上百個的異步請求都需要復制粘貼大量代碼,實在是非常的難受。
異步處理 redux-promise
當開發者都感受到痛苦后,自然會出頭解決問題,redux-promise 就是解決方案。
redux-promise 提供了一個簡單創建 promise action 的方法,並提供配套的 middleware 處理 promise 化的 action。整套代碼精簡了特別多,大致如下。
// data/action.js
import { createAction } from 'redux-promise'
import * as api from './api'
import ACTION_TYPE from '../constant/actionType'
export const fetchAction = createAction(ACTION_TYPE.FETCH_LIST, api.fetchList)
// data/reducer.js
import { fetchAction } from './action.js'
import ACTION_TYPE from '../constant/actionType'
const initState = {}
return (state = initState, action) => {
switch(action.type) {
case ACTION_TYPE.FETCH_LIST:
if(!action.error) {
// your success handler
} else {
// your failed handler
}
return state
default:
return state
}
}
/*
當然在基於 redux-actions,使用 handleAction 是可以省略 actionType.js 文件和定義的 ACTION_TYPE 的。
這里為了更清晰展現大家熟悉的 switch case 式的 reducer 就沒有展示出來了。
*/
這一看,代碼似乎精簡了許多,不需要對一個接口定義三個 actionType 了。但是 redux-promise 只能處理兩種狀態了,異步后的成功/失敗態。實際使用上,似乎也沒什么影響,畢竟 redux 天然支持同步 action,所以你就在調異步 action 前再調一個同步 action ,那個 action 來當作 FETCH_LIST_START 來使用。
所以我們把定義一個異步 Action 和對應的三個 actionType ,轉變為定義兩個 Action 和對應的 actionType,且破壞了一個異步 Action 的內聚性,導致本身一個異步 Action 造成的 state 變化,被拆成了兩個去維護,實在有失優雅和代碼相應的維護性。
redux-promise-utils
綜上所述,我認為 redux-thunk 還是一個更為合理的方案,我們是否能基於 redux-thunk 然后降低我們過於冗余的代碼呢。我們來看看到底 redux-thunk 有哪些麻煩的操作我們需要來優化。
- 每次都需要執行 promise 方法后,內部手動去 dispatch 各個狀態給 reducer 進行處理。
- 一個異步 Action 需要定義三個特定的 actionType。
- 需要維護一個的 actionType 文件,並提供給 action / reducer 使用。
最終 redux-promise-utils 處理了這三個問題,我們一起來看看最終代碼是怎樣的。
// data/action.js
import { createPromiseAction } from 'redux-promise-utils'
import * as api from './api'
export const fetchAction = createPromiseAction('fetchList', api.fetchList)
// data/reducer.js
import { createReducer } from 'redux-promise-utils'
import { fetchAction } from './action'
const initState = {}
const reducer = createReducer(initState)
// 獲取同步數據
.which(SYNC_ACTION, (state, action) => {
return action
})
// 獲取異步數據
.asyncWhich(fetchAction, {
start(state, action) {
return state
},
success(state, action) {
return state
},
fail(state, action) {
return state
}
})
// 構建 redux 需要的 reducer 方法
.build()
export default reducer
代碼精簡了許多,上述的三個問題都 say goodbye 了。除了 reducer 不再使用 switch case 模式去處理,並沒有特別多的區別。那到底 createPromiseAction / createReducer 做了哪些處理,下面會講到。
How
redux-promise-uitls 核心是基於 redux-thunk 和 redux-actions。
先來看看 createPromiseAction,核心就是內置三個執行狀態,並基於 promise 下自動 dispatch 相應的狀態。所以內部會按一定的規則,根據提供的 type 派生出三種狀態,分別是 ${type}_START / ${type}_SUCCESS / ${type}_FAILED。得益於 redux-thunk,即使不用配套提供的 createReducer 處理,也可以手動處理這三種狀態。但使用了 createReducer 則會更加順暢,包括不需要再維護 actionType,不用單獨處理派生出的三種狀態。
而 createReducer 內部,對 asyncWhich(type, handlerOptions) 捕獲到的 action,會根據 start/success/fail 三個函數自動處理,不需要寫多余的 type。which(type, handler) 捕獲的則是普通的同步函數,和以往的 reducer 寫法是一樣的。
createReducer 會對 type 執行一次 type.toString(),而 createPromiseAction 借鑒 redux-actions 的 createAction 一樣,會重寫 type.toString 方法,所以也不需要再獨立維護一套 actionType.js 相應的配置文件。
這些操作都是為了讓開發者可以逃離大部分無意義的 template 代碼,來提高每日的編碼體驗。
趕快來點星星吧
Github: redux-promise-utils
