思考html
Redux是一個很是實用的狀態管理庫,對於大多數使用React庫的開發者來講,Redux都是會接觸到的。在使用Redux享受其帶來的便利的同時, 咱們也深受其問題的困擾。vue
以前在另一篇文章Redux基礎中,就有提到如下這些問題react
裏面除了性能這一塊能夠利用react-redux進行優化,其餘的都是開發者不得不面對的問題,對於代碼有潔癖的人,囉嗦這一點確實是沒法忍受的。vuex
若是你使用過VUEX的話, 那麼對於它的API確定會相對喜歡不少,固然,vuex不是immutable,因此對於時間旅行這種業務不太友好。不過,咱們能夠本身實現一個具備vuex的簡潔語法和immutable屬性的redux-x(瞎命名)。redux
先看一下咱們想要的目標是什麼樣的?
首先, 咱們再./models裏面定義每一個子state樹,裏面帶有namespace、state、reducers、effects等屬性, 以下:數組
export default { // 命名空間 namespace: 'common', // 初始化state state: { loading: false, }, // reducers 同步更新 相似於vuex的mutations reducers: { updateLoadingStatus(state, action) { return { ...state, loading: action.payload } }, }, // reducers 異步更新 相似於vuex的actions efffects: { someEffect(action, store) { // some effect code ... ... // 將結果返回 return result } } }
經過上面的實現,咱們基本解決了Redux自己的一些瑕疵promise
1.在effects中存放的方法用於解決不支持異步、反作用的問題 2.經過合併reducer和action, 將模板代碼大大減小 3.具備分型結構(namespace),而且中心化處理
首先,咱們只是在外層封裝了一層API方便使用,那麼說到底,傳給redux的combineReducers仍是一個redux對象。另一個則是要處理反作用的話,那就必須使用到了中間件,因此最後咱們暴露出來的函數的返回值應該具備上面兩個屬性,以下:app
import reduxSimp from '../utils/redux-simp' // 內部實現 import common from './common' // models文件下common的狀態管理 import user from './user' // models文件下user的狀態管理 import rank from './rank' // models文件下rank的狀態管理 const reduxX = reduxSimp({ common, user, rank }) export default reduxX
const store = createStore( combineReducers(reduxX.reducers), // reducers樹 {}, applyMiddleware(reduxX.effectMiddler) // 處理反作用中間件 )
第一步, 咱們先實現一個暴露出來的函數reduxSimp,經過他對model裏面各個屬性進行加工,大概的代碼以下:異步
const reductionReducer = function() { // somecode } const reductionEffects = function() { // somecode } const effectMiddler = function() { // somecode } /** * @param {Object} models */ const simplifyRedux = (models) => { // 初始化一個reducers 最後傳給combinReducer的值 也是最終還原的redux const reducers = {} // 遍歷傳入的model const modelArr = Object.keys(models) modelArr.forEach((key) => { const model = models[key] // 還原effect reductionEffects(model) // 還原reducer,同時經過namespace屬性處理命名空間 const reducer = reductionReducer(model) reducers[model.namespace] = reducer }) // 返回一個reducers和一個專門處理反作用的中間件 return { reducers, effectMiddler } }
對於effects, 使用的時候以下(沒什麼區別):函數
props.dispatch({ type: 'rank/fundRankingList_fetch', payload: { fundType: props.fundType, returnType: props.returnType, pageNo: fund.pageNo, pageSize: 20 } })
還原effects的思路大概就是先將每個model下的effect收集起來,同時加上命名空間做爲前綴,將反作用的key即type 和相對應的方法value分開存放在兩個數組裏面,而後定義一箇中間件,每當有一個dispatch的時候,檢查key數組中是否有符合的key,若是有,則調用對應的value數組裏面的方法。
// 常量 分別存放反作用的key即type 和相對應的方法 const effectsKey = [] const effectsMethodArr = [] /** * 還原effects的函數 * @param {Object} model */ const reductionEffects = (model) => { const { namespace, effects } = model const effectsArr = Object.keys(effects || {}) effectsArr.forEach((effect) => { // 存放對應effect的type和方法 effectsKey.push(namespace + '/' + effect) effectsMethodArr.push(model.effects[effect]) }) } /** * 處理effect的中間件 具體參考redux中間件 * @param {Object} store */ const effectMiddler = store => next => (action) => { next(action) // 若是存在對應的effect, 調用其方法 const index = effectsKey.indexOf(action.type) if (index > -1) { return effectsMethodArr[index](action, store) } return action }
reducers的應用也是和原來沒有區別:
props.dispatch({ type: 'common/updateLoadingStatus', payload: true })
代碼實現的思路就是最後返回一個函數,也就是咱們一般寫的redux函數,函數內部遍歷對應命名空間的reducer,找到匹配的reducer執行後返回結果
/** * 還原reducer的函數 * @param {Object} model 傳入的model對象 */ const reductionReducer = (model) => { const { namespace, reducers } = model const initState = model.state const reducerArr = Object.keys(reducers || {}) // 該函數即redux函數 return (state = initState, action) => { let result = state reducerArr.forEach((reducer) => { // 返回匹配的action if (action.type === `${namespace}/${reducer}`) { result = model.reducers[reducer](state, action) } }) return result } }
最終的代碼以下,加上了一些錯誤判斷:
// 常量 分別存放反作用的key即type 和相對應的方法 const effectsKey = [] const effectsMethodArr = [] /** * 還原reducer的函數 * @param {Object} model 傳入的model對象 */ const reductionReducer = (model) => { if (typeof model !== 'object') { throw Error('Model must be object!') } const { namespace, reducers } = model if (!namespace || typeof namespace !== 'string') { throw Error(`The namespace must be a defined and non-empty string! It is ${namespace}`) } const initState = model.state const reducerArr = Object.keys(reducers || {}) reducerArr.forEach((reducer) => { if (typeof model.reducers[reducer] !== 'function') { throw Error(`The reducer must be a function! In ${namespace}`) } }) // 該函數即redux函數 return (state = initState, action) => { let result = state reducerArr.forEach((reducer) => { // 返回匹配的action if (action.type === `${namespace}/${reducer}`) { result = model.reducers[reducer](state, action) } }) return result } } /** * 還原effects的函數 * @param {Object} model */ const reductionEffects = (model) => { const { namespace, effects } = model const effectsArr = Object.keys(effects || {}) effectsArr.forEach((effect) => { if (typeof model.effects[effect] !== 'function') { throw Error(`The effect must be a function! In ${namespace}`) } }) effectsArr.forEach((effect) => { // 存放對應effect的type和方法 effectsKey.push(namespace + '/' + effect) effectsMethodArr.push(model.effects[effect]) }) } /** * 處理effect的中間件 具體參考redux中間件 * @param {Object} store */ const effectMiddler = store => next => (action) => { next(action) // 若是存在對應的effect, 調用其方法 const index = effectsKey.indexOf(action.type) if (index > -1) { return effectsMethodArr[index](action, store) } return action } /** * @param {Object} models */ const simplifyRedux = (models) => { if (typeof models !== 'object') { throw Error('Models must be object!') } // 初始化一個reducers 最後傳給combinReducer的值 也是最終還原的redux const reducers = {} // 遍歷傳入的model const modelArr = Object.keys(models) modelArr.forEach((key) => { const model = models[key] // 還原effect reductionEffects(model) // 還原reducer,同時經過namespace屬性處理命名空間 const reducer = reductionReducer(model) reducers[model.namespace] = reducer }) // 返回一個reducers和一個專門處理反作用的中間件 return { reducers, effectMiddler } } export default simplifyRedux
如何結合Immutable.js使用?
個人博客即將同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=368b4t649o8wg