preface

Redux is a very useful state management library that most developers using the React library will come into contact with. While we enjoy the convenience of using Redux, we are also plagued by its problems.

The problem of story

The following issues were addressed earlier in another Redux basics article

  • Pure. Redux only supports synchronization, making state predictable and easy to test. But instead of dealing with asynchronous, side-effect cases, leaving it to other middleware, such as redux-thunk, redux-promise, redux-saga, and so on, the choice can be confusing
  • Long-winded. Those of you who have written Redux know that action\ Reducer \ and your business code are very verbose and have a lot of template code. But it’s also about making the flow of data clear.
  • Performance. Brutally, cascading the view (optimized using React-Redux).
  • Parting. Native Redux-React has no fractal structure and centralized store

With the exception of performance, which can be optimized with React-Redux, developers have to deal with the rest of the problem, which is intolerable for the cleanliness freak.

Project objectives

If you’ve ever used VUEX, you’ll probably like the API a lot. Of course, VUEX is not immutable, so it’s not very friendly to the business of time travel. However, we can implement a redux-x (blind naming) with vuex’s compact syntax and immutable property by ourselves.

What do we want to do first? First, we defined each sub-state tree in./ Models with namespace, State, reducers, effects and other attributes as follows:

exportDefault {// Namespace namespace:'common'State: {loading:falseVuex mutations Reducers: {updateLoadingStatus(state, action) {return{... State, loading: action.payload}},}, // reducers update actions similar to vuex efffects: { someEffect(action, store) { // some effect code ... . // Return the resultreturn result
    }
  }
}

Copy the code

With the above implementation, we have basically solved some of Redux’s flaws

1. Methods stored in Effects are used to solve problems 2 that do not support asynchrony and side effects. By merging the Reducer and actions, the template code was significantly reduced by 3. It has a typing structure (namespace) and centralized processingCopy the code

How to implement

Exposed interface redux-x

First of all, we just wrapped a layer of API in the outer layer for easy use, so after all, the combineReducers passed to Redux is still a Redux object. The other is that middleware must be used to handle side effects, so the return value of the exposed function should have the above two attributes, as follows:

import reduxSimp from '.. /utils/redux-simp'// Internally implement import common from'./common'// Common state management in models file import user from'./user'// Import rank from user'./rank'Const reduxX = reduxSimp({common, user, rank})export default reduxX
Copy the code
Const store = createStore(combineReducers(reduxx.reducers), // Reducers {}, ApplyMiddleware (reduxx.effectMiddler)Copy the code

The first step is to implement an exposed function, reduxSimp, through which to process each attribute in the model, the approximate code is as follows:

const reductionReducer = function() { // somecode }
const reductionEffects = function() { // somecode }
const effectMiddler = function() {// somecode} /** * @param {Object} Models */ const simplifyRedux = (Models) => {// Initialize a reducers Redux const reducers = {} // Go through the incoming model const modelArr = object.keys (models) Modelarr. forEach((key) => {const model = Models [key] // reductionEffects(model) // reduce reducer, ReductionReducer (model) reducers[model.namespace] = Reducer}) // Return a reducers and a middleware dedicated to handling side effectsreturn {
    reducers,
    effectMiddler
  }
}
Copy the code

Reduction effects

For Effects, use the following (no difference) :

props.dispatch({
  type: 'rank/fundRankingList_fetch',
  payload: {
    fundType: props.fundType,
    returnType: props.returnType,
    pageNo: fund.pageNo,
    pageSize: 20
  }
})
Copy the code

The idea of restoring Effects is to collect effect under each model, add namespace as prefix, store side effect key (type) and corresponding method value separately in two arrays, and then define a middleware. When there is a dispatch, Check if there is a matching key in the key array, and if so, call the method in the corresponding value array.

// The constant stores the side effect key respectively, i.etypeConst effectsKey = [] const effectsMethodArr = [] const effectsMethodArr = @param {Object} model */ const reductionEffects = (model) => { const { namespace, Effects} = model const effectsArr = Object. The keys (effects | | {}) effectsArr. ForEach ((effect) = > {/ / deposit corresponding effecttypeAnd the method effectskey. push(namespace +'/'+ effect) effectsmethodarr.push (model.effects[effect])})} ** ** Redux middleware * @param {Object} store */ Const effectMiddler = store => next => (action) => {next(action) Const index = effectskey.indexof (action.type)if (index > -1) {
    return effectsMethodArr[index](action, store)
  }
  return action
}
Copy the code

Reduction of reducers

The application of Reducers is also the same as before:

props.dispatch({ type: 'common/updateLoadingStatus', payload: true })
Copy the code

The idea of code implementation is to return a function at last, namely the redux function we usually write. The function internally traverses the reducer of the corresponding namespace and returns the result after finding the matched reducer execution

/** * reductionReducer = (model) => {const {namespace, Reducers} = model const initState = model. The state const reducerArr = Object. The keys (reducers | | {}) / / this function is redux functionreturn (state = initState, action) => {
    letResult = state reducerarr.foreach ((reducer) => {// Return matched actionsif (action.type === `${namespace}/${reducer}`) {
        result = model.reducers[reducer](state, action)
      }
    })
    return result
  }
}
Copy the code

The final code

The final code looks like this, with some misjudgments added:

// The constant stores the side effect key respectively, i.etypeConst effectsKey = [] const effectsMethodArr = [] /** * Reduce reducer function * @param {Object} model  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}')}}) // This function is the redux functionreturn (state = initState, action) => {
    letResult = state reducerarr.foreach ((reducer) => {// Return matched actionsif (action.type === `${namespace}/${reducer}`) {
        result = model.reducers[reducer](state, action)
      }
    })
    returnResult}} /** * Reduce 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) =>typeAnd the method effectskey. push(namespace +'/'+ effect) effectsmethodarr.push (model.effects[effect])})} ** ** Redux middleware * @param {Object} store */ Const effectMiddler = store => next => (action) => {next(action) 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! '} // Initialize a reducers and the final value passed to combinReducer is also the final reduced Redux const reducers = {} // loop through the passed Model const modelArr = Keys (Models) modelarr.foreach ((key) => {const Model = Models [key] // reductionEffects(Model) // Reduction of reducer, ReductionReducer (model) reducers[model.namespace] = Reducer}) // Return a reducers and a middleware dedicated to handling side effectsreturn {
    reducers,
    effectMiddler
  }
}

export default simplifyRedux
Copy the code

thinking

How to use Immutable?