從Vue換到React+Redux進行開發已經有半年多的時間,總的來講體驗是很好的,對於各類邏輯和業務組件的抽象實在是方便的不行,高階組件,洋蔥模型等等給我帶來了不少編程思想上的提高。可是在使用Redux開發的過程當中仍是感受不太順手,本文將闡述我是如何對Redux進行一步步「改造」以適應我的和團隊開發需求的。
過程當中的示例和結果放在了easy-redux,歡迎star。
原文連接前端
在使用Redux開發的過程當中逐漸發現,雖然咱們已經將UI組件和業務組件儘量的進行抽離,儘量的保證reduceractions的複用性,
可是咱們仍是會花費大量的時間來書寫近乎相同的代碼。尤爲是咱們組內但願秉承一個原則:儘可能將全部的操做及狀態修改都交由action來執行,方便咱們對問題進行定位。當我在某大型前端交流羣裏還看到「不用Redux,不想加班」的說法時,不得不感嘆,須要作些努力來解決我目前的問題了。react
是的,Redux對我來講,太複雜了git
針對一個簡單的操做,咱們須要進行如下步驟來完成:github
1.定義action編程
export const CHANGE_CONDITION = 'CHANGE_CONDITION'
2.定義一個對應的action建立函數redux
export const changeCondition = condition => ({ type: CHANGE_CONDITION, condition })
3.引入action, 定義reducer, 在複雜的switch語句中,對對象進行更改api
import { CHANGE_CONDITION } from '@actions' const condition = (state = initCondition, action) => { switch(action.type) { case CHANGE_CONDITION: return ... default: return state } }
4.在須要時,引入action建立函數, 並將對應的state進行鏈接app
import { changeCondition } from 'actions' @connect(...)
我只是想作一個簡單的狀態修改呀!異步
可能咱們會說,這樣拆分可以保證咱們整個項目的規範化,加強業務的可預測性與錯誤定位能力。
可是隨着項目的不斷擴大,每一個頁面都有一堆action須要我加的時候,實在是讓人頭痛啊。函數
並且,針對請求的修改,咱們每每要把action拆分紅START,SUCCESS,FAILED三種狀態,reducer裏須要進行三次修改。並且每每
針對這些修改,咱們進行的處理都是大體相同的:更新loading狀態,更新數據,更新錯誤等等。
因此說,咱們如何在保證redux的設計原則以及項目規範性上,對其進行「簡化改造」,是我這裏須要解決的問題。
針對請求的處理,我以前也寫過一篇文章優雅地減小redux請求樣板代碼, 經過封裝了一個redux中間件react-fetch-middleware
來對請求代碼進行優化。
大體思路以下:
1.action建立函數返回的內容爲一個包含請求信息的對象,幷包含須要分發的三個action,這三個action能夠經過actionCreator進行建立
import { actionCreator } from 'redux-data-fetch-middleware' // create action types export const actionTypes = actionCreator('GET_USER_LIST') export const getUserList = params => ({ url: '/api/userList', params: params, types: actionTypes, // handle result handleResult: res => res.data.list, // handle error handleError: ... })
2.在redux中間件中,針對以上格式的action進行處理,首先進行請求,並分發請求開始的action,
在請求成功和失敗時,分別分發對應的action
const applyFetchMiddleware = ( fetchMethod = fetch, handleResponse = val => val, handleErrorTotal = error => error ) => store => next => action => { // 判斷action的格式 if (!action.url || !Array.isArray(action.types)) { return next(action) } // 獲取傳入的三個action const [ START, SUCCESS, FAILED ] = action.types // 在不一樣狀態分發action, 並傳入loading,error狀態 next({ type: START, loading: true, ...action }) return fetchMethod(url, params) .then(ret => { next({ type: SUCCESS, loading: false, payload: handleResult(ret) }) }) .catch(error => { next({ type: FAILED, loading: false, error: handleError(error) }) }) }
3.將reducer進行對應的默認處理,使用reducerCreator建立的函數中自動進行對應處理,而且提供二次處理的機制
const [ GET, GET_SUCCESS, GET_FAILED ] = actionTypes // 會在這裏自動處理分發的三個action const fetchedUserList = reducerCreator(actionTypes) const userList = (state = { list: [] }, action => { // 二次處理 switch(action.type) { case GET_SUCCESS: return { ...state, action.payload } } }) export default combineReducers({ userList: fetchedUserList(userList) })
通過前一步對請求的簡化,咱們已經能夠在保證不改變redux原則和書寫習慣的基礎上,極大的簡化請求樣板代碼。
針對普通的數據處理,咱們是否是能夠更進一步?
很高興看到這個庫: Rematch
, 對Redux Api進行了極大的簡化。
可是有些功能和改進並非咱們想要的,所以我僅對我須要的功能和改進點進行說明,並用本身的方式進行實現。咱們來一步步看看
咱們須要解決的問題以及如何解決的。
針對reducer,咱們不但願重複的引用定義的各個action, 而且去掉冗長的switch判斷。其實咱們能夠將其進行反轉拆分,將每個action定義爲標準化的reducer, 在其中對state進行處理.
const counter = { state: 1, reducers: { add: (state, payload) => state + payload, sub: (state, payload) => state - payload } }
去掉以前的action和action建立函數,直接在actions中進行數據處理,並與對應的reducer進行match
export const addNum = num => dispatch => dispatch('/counter/add', num)
咱們會看到,與reducer進行match時,咱們使用了'/counter/add'這種命名空間的方式,
目的是在保證其直觀性的同時,保證action與其reducer是一一對應的。
咱們能夠經過加強的combinceReducer進行命名空間的設定:
const counter1 = { ... } const counter2 = { ... } const counters = combinceReducer({ counter1, counter2 }) const list = { ... } // 設置大reducer的根命名空間 export default combinceReducer({ counters, list }, '/test') // 咱們能夠經過這樣來訪問 dispatch('/test/counters/counter1/add')
針對請求這些異步action,咱們能夠參考咱們以前的修改, dispatch一個對象
export const getList = params => dispatch => { return dispatch({ //對應到咱們想要dispatch的命名空間 action: '/list/getList', url: '/api/getList', params, handleResponse: res => res.data.list, handleError: error => error }) }
同時,咱們在reducer中進行簡單的處理便可,依舊能夠進行默認的三個狀態處理
const list = { // 定義reducer頭,會自動變爲getList(開始請求),getListSuccess,getListFailed // 並進行loading等默認處理 fetch: 'getList' state: { list: [] }, reducers: { // 二次處理 getListSuccess: (state, payload) => ({ ...state, list: payload }) } }
咱們會看到,咱們已經將redux的api進行了極大的簡化,可是依舊保持了原有的結構。目的有如下幾點:
原有的數據流變成了這樣:
所以,咱們是在redux的基礎上進行二次封裝的,咱們依然保證了原有的Redux數據流,保證數據的可回溯性,加強業務的可預測性與錯誤定位能力。這樣能極大的保證與老項目的兼容性,因此咱們須要作的,只是對action和reducer的轉化工做
咱們經過新的combinceReducer,將新的格式,轉化爲以前的reducer格式,並保存各個reducer其和對應的action的命名空間。
代碼簡單示意:
//獲取各reducers裏的方法 const actionNames = Object.keys(reducers) const resultActions = actionNames.map(action => { const childNamespace = `${namespace}/${action}` // 將action存入namespace Namespace.setActionByNamespace(childNamespace) return { name: Namespace.toAction(childNamespace), fn: reducers[action] } }) // 返回默認格式 return (state = inititalState, action) => { // 查詢action對應的新的reducer裏的方法 const actionFn = resultActions.find(cur => cur.name === action.type) if (actionFn) { return actionFn.fn && actionFn.fn(state, action.payload) } return state }
咱們須要把這樣格式的函數,轉化成這樣
count => dispatch => dispatch('/count/add', count) //or params => dispatch => { dispatch('/count/add', 1), dispatch('/count/sub', 2) } //結果 count => ({ type: 'count_add', payload: count })
這裏的處理比較複雜,其實就是改造咱們的dispatch函數
action => params => (dispatch, getstate) => { const retDispatch = (namespace, payload) => { return dispatch({ type: Namespace.get(namespace), payload }) } return action(params)(retDispatch, getstate) }
經過對Redux Api的改造,至關於二次封裝,已經很大的簡化了目前在項目中的樣板代碼,而且在項目中很順暢的使用。
針對整個過程,其實還有幾個能夠改進的地方:
有興趣的話,歡迎探討~ 附上github easy-redux