摘要:但是在使用開發的過程中還是感覺不太順手,本文將闡述我是如何對進行一步步改造以適應個人和團隊開發需求的。所以說,我們如何在保證的設計原則以及項目規范性上,對其進行簡化改造,是我這里需要解決的問題。
從Vue換到React+Redux進行開發已經有半年多的時間,總的來說體驗是很好的,對于各種邏輯和業務組件的抽象實在是方便的不行,高階組件,洋蔥模型等等給我帶來了很多編程思想上的提升。但是在使用Redux開發的過程中還是感覺不太順手,本文將闡述我是如何對Redux進行一步步“改造”以適應個人和團隊開發需求的。
過程中的示例和結果放在了easy-redux,歡迎star。
原文鏈接
在使用Redux開發的過程中逐漸發現,雖然我們已經將UI組件和業務組件盡可能的進行抽離,盡可能的保證reduceractions的復用性,
但是我們還是會花費大量的時間來書寫近乎相同的代碼。尤其是我們組內希望秉承一個原則:盡量將所有的操作及狀態修改都交由action來執行,方便我們對問題進行定位。當我在某大型前端交流群里還看到“不用Redux,不想加班”的說法時,不得不感嘆,需要做些努力來解決我目前的問題了。
是的,Redux對我來說,太復雜了
針對一個簡單的操作,我們需要進行以下步驟來完成:
1.定義action
export const CHANGE_CONDITION = "CHANGE_CONDITION"
2.定義一個對應的action創建函數
export const changeCondition = condition => ({ type: CHANGE_CONDITION, condition })
3.引入action, 定義reducer, 在復雜的switch語句中,對對象進行更改
import { CHANGE_CONDITION } from "@actions" const condition = (state = initCondition, action) => { switch(action.type) { case CHANGE_CONDITION: return ... default: return state } }
4.在需要時,引入action創建函數, 并將對應的state進行連接
import { changeCondition } from "actions" @connect(...)
我只是想做一個簡單的狀態修改呀!
可能我們會說,這樣拆分能夠保證我們整個項目的規范化,增強業務的可預測性與錯誤定位能力。
但是隨著項目的不斷擴大,每個頁面都有一堆action需要我加的時候,實在是讓人頭痛啊。
而且,針對請求的修改,我們往往要把action拆分成START,SUCCESS,FAILED三種狀態,reducer里需要進行三次修改。而且往往
針對這些修改,我們進行的處理都是大致相同的:更新loading狀態,更新數據,更新錯誤等等。
所以說,我們如何在保證redux的設計原則以及項目規范性上,對其進行“簡化改造”,是我這里需要解決的問題。
使用middleware簡化請求針對請求的處理,我之前也寫過一篇文章優雅地減少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 Api
經過前一步對請求的簡化,我們已經可以在保證不改變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 } }2.復雜的action創建函數
去掉之前的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")3.別忘了請求
針對請求這些異步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進行了極大的簡化,但是依舊保持了原有的結構。目的有以下幾點:
依舊遵循默認原則,保證項目的規范性
通過約定和命名空間來保證action和reducer的match
底層還是使用redux實現,這些只不過是語法糖
保證與老項目的兼容性
原有的數據流變成了這樣:
因此,我們是在redux的基礎上進行二次封裝的,我們依然保證了原有的Redux數據流,保證數據的可回溯性,增強業務的可預測性與錯誤定位能力。這樣能極大的保證與老項目的兼容性,所以我們需要做的,只是對action和reducer的轉化工作
1.combinceReducer返回原格式的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 }2.新的action創建函數最終dispatch出原格式的action
我們需要把這樣格式的函數,轉化成這樣
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的改造,相當于二次封裝,已經很大的簡化了目前在項目中的樣板代碼,并且在項目中很順暢的使用。
針對整個過程,其實還有幾個可以改進的地方:
actions的轉化過程,交由中間件處理
性能問題,目前相當于多做了一層轉化,但是目前影響不大
reducer,action復用
有興趣的話,歡迎探討~ 附上github easy-redux
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95246.html
摘要:手挽手帶你學入門四檔用人話教你,理解架構,以及運用在中。學完這一章,你就可以開始自己的項目了。結合搭建基礎環境我們上一章講過了的原理,內部是有一個的,只有才可以控制它變化。 手挽手帶你學React入門四檔,用人話教你react-redux,理解redux架構,以及運用在react中。學完這一章,你就可以開始自己的react項目了。 視頻教程 上一篇我們自己實現了Redux,這一篇我們來...
摘要:然后循環調用中的更新函數,更新函數一般是我們的渲染函數,函數內部會調用來獲取數據,所以頁面會更新。前言 前幾天寫了一篇react另一個狀態管理工具Unstated的源碼解析。 開啟了我的看源碼之路。想一想用了好長時間的redux,但從沒有深究過原理,遇到報錯更是懵逼,所以就啃了一遍它的源碼,寫了這篇文章, 分享我對于它的理解。 API概覽 看一下redux源碼的index.js,看到了我們最...
摘要:然后循環調用中的更新函數,更新函數一般是我們的渲染函數,函數內部會調用來獲取數據,所以頁面會更新。 歡迎訪問個人網站:https://www.neroht.com/ 前言 前幾天寫了一篇react另一個狀態管理工具Unstated的源碼解析。開啟了我的看源碼之路。想一想用了好長時間的redux,但從沒有深究過原理,遇到報錯更是懵逼,所以就啃了一遍它的源碼,寫了這篇文章,分享我對于它的理...
摘要:最近在做一些梳理,把平時記錄的一些筆記和實踐整理成完整的短篇技術文章。下一步我們要處理的是樣式。相比最初的版本多了一些代碼,因為我們把一些關鍵邏輯拆分到了不同的組件中,干凈并不意味著少。 最近在做一些梳理, 把平時記錄的一些筆記和實踐整理成完整的短篇技術文章。 這篇主要說一下如何精簡你的React Components showImg(https://segmentfault.com...
閱讀 1179·2023-04-26 02:38
閱讀 1473·2021-11-22 09:34
閱讀 1180·2021-09-26 10:19
閱讀 3159·2019-08-29 17:15
閱讀 3515·2019-08-29 12:27
閱讀 1715·2019-08-26 13:51
閱讀 1858·2019-08-26 13:47
閱讀 1010·2019-08-26 12:20