preface

Redux should be relatively familiar to those on the front end of the React stack. The simplicity of its code and clever design has been praised by everyone. In addition, Redux’s notes are perfect and easy to read. Originally was also forced to read the source code, now also forget almost. I started rereading Redux because I was planning to do some work on it recently, and it was very profitable.

The basic concepts of Redux are not described in detail here. You can refer to the Redux Chinese documentation.

Read the source code

There are a lot of bulls who have provided a lot of reading experience. I feel that it is not advisable to forcibly read the source code at the beginning, as I had read the first time redux, can only say that food tasteless, now forget all.

Should be more familiar with its basic usage, have a question or interest to read it better, combined with documents or examples, the complete process to go.

In addition to direct source repository clone down, local run, really do not understand the breakpoint with go in.

To do not understand the place, may be some methods are not familiar with, at this time to find its specific usage and purpose

Really do not understand can be combined with the online source code examples, and other people’s ideas contrast, see where their understanding of deviation.

In a word, I hope that after reading it, I will be inspired and have a deeper understanding and learning, rather than just saying and reading it.

Redux provides the following methods:

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}
Copy the code

The following article looks at the implementation of each method in the order of the Redux Chinese documentation examples.

The action and actionCreater

Definitions and Concepts

Actions are essentially ordinary JavaScript objects and the actionCreater in Redux is the method that generates the action

/ / is addTodo actionCreater
function addTodo(text) {
  // The object of return is action
  return {
    type: ADD_TODO,
    text
  }
}
Copy the code

In a traditional Flux implementation, a Dispatch is typically triggered when an action creation function is called. In Redux, you simply pass the results of the action creation function to the Dispatch () method to initiate a dispatch process.

dispatch(addTodo(text))
/ / or
const boundAddTodo = text= > dispatch(addTodo(text))
Copy the code

Of course, in practice, we don’t need to dispatch manually every time (in this case simply synchronizing actionCreater). React-redux provides connect() to do this for us.

With bindActionCreators(), you can automatically bind multiple action creators to the Dispatch () method.

Without touching on Connect, let’s take a look at how bindActionCreators came about.

Before we look, we can venture a guess at what we would do if we were to provide a warper and bind two methods together:

function a (){
   / *... * /  
};
function b(f){
  / *... * / 
  return f()
}
Copy the code

B calls a (forget all else) and binds it with a c

function c(){
    return (a)= > b(a)
}
Copy the code

That’s what it looks like. So let’s see how it works

bindActionCreators()

First look at the source code:

// Bind a single actionCreator
function bindActionCreator(actionCreator, dispatch) {
  // Dispatch the method to avoid creating manual calls to the action.
  return (. args) = >dispatch(actionCreator(... args)) }export default function bindActionCreators(actionCreators, dispatch) {
   // function a single actionCreator calls bindActionCreator directly
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
   // check, otherwise throw error
  if (typeofactionCreators ! = ='object' || actionCreators === null) {
    throw new Error(Error message)}// Get the keys array for traversal
  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    // Verify the binding in turn
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  / / return
  return boundActionCreators
}
Copy the code

The method is divided into two parts

The first is bindActionCreator

Encapsulate a single ActionCreator method

   function bindActionCreator(actionCreator, dispatch) {
  // Dispatch the method to avoid creating manual calls to the action.
  return (. args) = >dispatch(actionCreator(... args)) }Copy the code

The actionCreators expectation of bindActionCreators is an object, actionCreator, and you can imagine that you would iterate over the properties of that object and call bindActionCreator in turn

The following bindActionCreators action is to handle this object

  • Typeof actionCreators === ‘function’ separate method, which ends with a direct call to bindActionCreator
  • If it is not an object or null, an error is thrown
  • For the object, iterate over the key, get the packaged boundActionCreators and return it
  // function a single actionCreator calls bindActionCreator directly
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
   // check, otherwise throw error
  if (typeofactionCreators ! = ='object' || actionCreators === null) {
    throw new Error(Error message)}// Get the keys array for traversal
  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    // Verify the binding in turn
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }  
Copy the code

This results in the bundled actionCreators, without requiring a manual call to Dispatch (in the simple case of synchronization)

reducer

Actions only describe what happens and provide source data. Reducer is needed to deal with the specific actions (detailed introduction will be skipped). In Redux applications, all states are stored in a single object

When reducer processes multiple ATcion, it is rather tedious and needs to be disassembled as follows:

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false}]})case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: state.todos.map((todo, index) = > {
          if (index === action.index) {
            return Object.assign({}, todo, {
              completed: !todo.completed
            })
          }
          return todo
        })
      })
    default:
      return state
  }
}  
Copy the code

When splitting is needed, it should be better for each reducer to deal only with the state of the relevant part rather than all the state, for example:

/ / reducer1
 function reducer1(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return state.reducer1.a 
      // Compare state to just state.reducer1, which is obviously better
      return state.a
 }
Copy the code

Each Reducer is responsible for managing only its share of the global state. State parameters of each Reducer are different, corresponding to the part of state data it manages

In this way, the reducer input parameters need to be managed in the main function, as follows:

function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}
Copy the code

Of course redux provides the combineReducers() method

import { combineReducers } from 'redux'

const todoApp = combineReducers({
  visibilityFilter,
  todos
})
Copy the code

So let’s take a look at combineReducers how to achieve

combineReducers

Let me put up the full code

export default function combineReducers(reducers) {
  // Obtain the reducer key. If the reducer key is not processed, it is the reducer method name
  var reducerKeys = Object.keys(reducers)

  var finalReducers = {}
  // Construct finalReducers which is the total reducer
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  var finalReducerKeys = Object.keys(finalReducers)

  var sanityError
  try {
    // check the specification
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }

  return function combination(state = {}, action) {
    if (sanityError) {
      throw sanityError
    }
    // Alarm information
    if(process.env.NODE_ENV ! = ='production') {
      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action)
      if (warningMessage) {
        warning(warningMessage)
      }
    }
    /** * When action changes, * traverse finalReducers, execute reducer and assign a value to nextState, * return current or nextState ** / based on whether the state of the corresponding key has changed
    // state Indicates whether to change the flag
    var hasChanged = false
    var nextState = {}
    // in sequence
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      // Get the state attribute of the corresponding key
      var previousStateForKey = state[key]
      // Only the corresponding key data is processed
      var nextStateForKey = reducer(previousStateForKey, action)
      // Cannot return undefined, otherwise throw error
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // Assign the new state to the nextState object
      nextState[key] = nextStateForKey
      // Whether to change the processinghasChanged = hasChanged || nextStateForKey ! == previousStateForKey }// Return state as appropriate
    return hasChanged ? nextState : state
  }
}
Copy the code

The ginseng

Let’s start with the input parameter: reducers

  • That is the set of reducer objects that need to be merged.
  • This can be obtained by importing * as reducers
  • The reducer should also handle the default case. If state is undefined or undefined action, it should not return undefined. What is returned is a total Reducer that can call each incoming method and pass in the corresponding state properties.

Traverse the reducers

Since it’s a collection of objects, we’re going to iterate over them, so the first few steps are going to do that.

The objective of the reducer key is to process the state of the corresponding key for each submethod
  var reducerKeys = Object.keys(reducers)

  var finalReducers = {}
  // Construct finalReducers which is the total reducer
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
// Get finalReducers for the following traversal call
var finalReducerKeys = Object.keys(finalReducers)
Copy the code

Then there is the specification validation, which is required as a framework and can be skipped

combination

Return a function

  • When the action is dispatched, this method mainly distributes different states to the corresponding Reducer process and returns the latest state

  • First, identify the variable:

    // state Indicates whether to change the flag
    var hasChanged = false
    var nextState = {}  
Copy the code
  • Do the traversal finalReducers to save the original previousStateForKey

  • Then distribute corresponding attributes to corresponding reducer for processing to obtain nextStateForKey

    Check nextStateForKey first. Because reducer requires compatibility, undefined is not allowed, and errors are cast if undefined.

    Normally nextState for Key is assigned to the corresponding key of nextState

  • Compare the two states to see if they are equal. If they are equal, hasChanged is set to true. At the end of the loop, a new state, nextState, is obtained

for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      // Get the state attribute of the corresponding key
      var previousStateForKey = state[key]
      // Only the corresponding key data is processed
      var nextStateForKey = reducer(previousStateForKey, action)
      // Cannot return undefined, otherwise throw error
      if (typeof nextStateForKey === 'undefined') {
         / /...
      }
      // Assign the new state to the nextState object
      nextState[key] = nextStateForKey
      // Whether to change the processinghasChanged = hasChanged || nextStateForKey ! == previousStateForKey }Copy the code

Return the old and new states according to hasChanged.

// Return state as appropriate
    return hasChanged ? nextState : state
Copy the code

Here the combineReducers end.

conclusion

Share half this time, which is a bit too much, and record the rest next time. Let’s improve ourselves and learn together.

Refer to the article

Redux Chinese document