Redux 處理異步 Action


redux-promise-utils

What

redux-promise-utils 是一個基於 redux-thunkredux-actions 的工具,符合 FSA 規范,方便開發者處理異步 Action,減少大量冗余的 template 代碼。

Why

redux 一開始的設計就是用於處理同步的 Action。通過調用 Action 函數后 dispatch 返回的 type 和 payload ,最終交由 reducer 處理。reducer 匹配到相應的 type 並進行處理。

說到 redux 處理異步 Action,最為人熟知的就是 redux-thunkredux-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 有哪些麻煩的操作我們需要來優化。

  1. 每次都需要執行 promise 方法后,內部手動去 dispatch 各個狀態給 reducer 進行處理。
  2. 一個異步 Action 需要定義三個特定的 actionType。
  3. 需要維護一個的 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-thunkredux-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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM