Redux series X: source code analysis

Time:2022-5-25

Write in front

The source code of Redux is very concise. Except that applymeddleware is difficult to understand, most of it is easy to understand.

Assuming that readers have a certain understanding of Redux, they will not popularize the concept and API of redux. It is suggested to read this part directlyOfficial documents

In addition, the Chinese annotated version of source code analysis has been uploaded to GitHub and can be downloadedClick to view。 The relevant sample code of this article can beClick to view

Overview of source code analysis

Download Redux and look at its directory structure.

npm install redux

What we need to care about here issrcThe directory, source code analysis and files that need to be concerned are all here

  • index.js: the Redux master file mainly exposes several core APIs
  • createStore.jscreateStoreDefinition of method
  • utils: various tool methods, among which applymiddleware, combinereducers and bindactioncreators are the core methods of Redux, and the rest pick, mapvalue and compose are common tool functions
➜  src git:(master) ✗ tree
.
├── createStore.js
├── index.js
└── utils
    ├── applyMiddleware.js
    ├── bindActionCreators.js
    ├── combineReducers.js
    ├── compose.js
    ├── isPlainObject.js
    ├── mapValues.js
    └── pick.js

Source code analysis: index js

It’s super simple. It exposes several core APIs. It’s gone

mport createStore from './createStore';
import combineReducers from './utils/combineReducers';
import bindActionCreators from './utils/bindActionCreators';
import applyMiddleware from './utils/applyMiddleware';
import compose from './utils/compose';

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
};

Source code analysis: createstore js

Paste the source code directly and make simple comments. look downredux.createStore(reducer, initialState)Call the document description, and you can basically understand the following code.

Special emphasis: although in several documents,createStore.jsThe number of lines of code is the largest, but it is the easiest to read. The following points are key

  1. redux.createStore(reducer, initialState)Reducer and initialstate are passed in, and a store object is returned.
  2. The store object exposes the dispatch, getstate and subscribe methods
  3. The store object gets the internal state through getstate()
  4. Initialstate is the initial state of the store. If it is not transmitted, it is undefined
  5. The store object modifies the internal state through the reducer
  6. When the store object is created, it will be actively called internallydispatch({ type: ActionTypes.INIT });To initialize the internal state. Through breakpoints or log printing, you can see that when the store object is created, the reducer will be called for initialization.
import isPlainObject from './utils/isPlainObject';

/**
 * These are private action types reserved by Redux.
 * For any unknown actions, you must return the current state.
 * If the current state is undefined, you must return the initial state.
 * Do not reference these action types directly in your code.
 */
//When initializing (when redux.createstore (reducer, initialstate)), the action passed That's it
export var ActionTypes = {
  INIT: '@@redux/INIT'
};

/**
 * Creates a Redux store that holds the state tree.
 * The only way to change the data in the store is to call `dispatch()` on it.
 *
 * There should only be a single store in your app. To specify how different
 * parts of the state tree respond to actions, you may combine several reducers
 * into a single reducer function by using `combineReducers`.
 *
 * @param {Function} reducer A function that returns the next state tree, given
 * the current state tree and the action to handle.
 *
 * @param {any} [initialState] The initial state. You may optionally specify it
 * to hydrate the state from the server in universal apps, or to restore a
 * previously serialized user session.
 * If you use `combineReducers` to produce the root reducer function, this must be
 * an object with the same shape as `combineReducers` keys.
 *
 * @returns {Store} A Redux store that lets you read the state, dispatch actions
 * and subscribe to changes.
 */
export default function createStore(reducer, initialState) {
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.');
  }

  var currentReducer = reducer;
  var currentState = initialState;
  var listeners = [];
  var isDispatching = false;

  /**
   * Reads the state tree managed by the store.
   *
   * @returns {any} The current state tree of your application.
   */
  //This method has nothing to say. It returns the current state
  function getState() {
    return currentState;
  }

  /**
   * Adds a change listener. It will be called any time an action is dispatched,
   * and some part of the state tree may potentially have changed. You may then
   * call `getState()` to read the current state tree inside the callback.
   *
   * @param {Function} listener A callback to be invoked on every dispatch.
   * @returns {Function} A function to remove this change listener.
   */
  //A very common way to add listening functions is when store Called when dispatching
  // store. Subscribe (listener) returns a method (unsubscribe) that can be used to cancel listening
  function subscribe(listener) {
    listeners.push(listener);
    var isSubscribed = true;

    return function unsubscribe() {
      if (!isSubscribed) {
        return;
      }

      isSubscribed = false;
      var index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  }

  /**
   * Dispatches an action. It is the only way to trigger a state change.
   *
   * The `reducer` function, used to create the store, will be called with the
   * current state tree and the given `action`. Its return value will
   * be considered the **next** state of the tree, and the change listeners
   * will be notified.
   *
   * The base implementation only supports plain object actions. If you want to
   * dispatch a Promise, an Observable, a thunk, or something else, you need to
   * wrap your store creating function into the corresponding middleware. For
   * example, see the documentation for the `redux-thunk` package. Even the
   * middleware will eventually dispatch plain object actions using this method.
   *
   * @param {Object} action A plain object representing “what changed”. It is
   * a good idea to keep actions serializable so you can record and replay user
   * sessions, or use the time travelling `redux-devtools`. An action must have
   * a `type` property which may not be `undefined`. It is a good idea to use
   * string constants for action types.
   *
   * @returns {Object} For convenience, the same action object you dispatched.
   *
   * Note that, if you use a custom middleware, it may wrap `dispatch()` to
   * return something else (for example, a Promise you can await).
   */
  //Errors will be reported in the following cases
  // 1.  The action passed in is not an object
  // 2.  The action passed in is an object, but the action Type is undefined
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      );
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      );
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.');
    }

    try {
      isDispatching = true;
      //That's it. Set currentstate to the value returned by reducer (currentstate, action)
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    //If there is a listener function, it will be called in sequence
    listeners.slice().forEach(listener => listener());

    //Finally, return the passed in action
    return action;
  }

  /**
   * Replaces the reducer currently used by the store to calculate the state.
   *
   * You might need this if your app implements code splitting and you want to
   * load some of the reducers dynamically. You might also need this if you
   * implement a hot reloading mechanism for Redux.
   *
   * @param {Function} nextReducer The reducer for the store to use instead.
   * @returns {void}
   */
  function replaceReducer(nextReducer) {
    currentReducer = nextReducer;
    dispatch({ type: ActionTypes.INIT });
  }

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  //
  // redux. When creating a createstore (reducer, initialstate), dispatch ({type: actiontypes. Init}) will be called internally;
  //To complete the initialization of state
  dispatch({ type: ActionTypes.INIT });

  //This is what is returned. There are only four methods
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer
  };
}

Source code analysis: combinereducers js

redux. The function of combinerreducers (reducermap) is to merge multiple reducer functions and return a new reducer function. Therefore, we can see that combinereducers returns a function, and the parameters of the function are also state and reducer.

You can first look at the pseudo code and feel the final store The state returned by getstate () looks like this{todos: xx, filter: xx}。 Simply put, the state is split into two parts, and the return value of todoreducer is assigned tostate.todos, the return value of filterreducer is assigned tostate.filter

function TodoReducer(state, action) {}
function FilterReducer(state, action) {}

var finalReducers = redux.combineReducers({
    todos: TodoReducer,
    filter: FilterReducer
});

It is also the code directly annotated. Remember several key points:

  1. Combine reducers (reducermap) passes in an object and returns a new reducer. The calling method is the same as that of an ordinary reducer. It also passes in state and action.
  2. Split the state of the store through combinereducers,
  3. The key of reducermap is the key of state, and the value returned by calling the corresponding reducer is the value corresponding to this key. As in the above example, state todos == TodoReducer(state, action)
  4. redux. When createstore (final reducers, initialstate) is called, the state will also be initialized. This initialization is not much different from that through an ordinary reducer. For example, if initialstate Todos = undefined, then the initial state passed in by todoreducer (state, action) is undefined; If initialstate Todos = [], then the initial incoming state of todoreducer (state, action) is [];
  5. store. Dispatch (action), in finalreducers, will traverse the entire reducermap, call each reducer in turn, and assign the sub state returned by each reducer to the key corresponding to the state.
import { ActionTypes } from '../createStore';
import isPlainObject from '../utils/isPlainObject';
import mapValues from '../utils/mapValues';
import pick from '../utils/pick';

/* eslint-disable no-console */

function getUndefinedStateErrorMessage(key, action) {
  var actionType = action && action.type;
  var actionName = actionType && `"${actionType.toString()}"` || 'an action';

  return (
    `Reducer "${key}" returned undefined handling ${actionName}. ` +
    `To ignore an action, you must explicitly return the previous state.`
  );
}

function getUnexpectedStateKeyWarningMessage(inputState, outputState, action) {
  var reducerKeys = Object.keys(outputState);
  var argumentName = action && action.type === ActionTypes.INIT ?
    'initialState argument passed to createStore' :
    'previous state received by the reducer';

  if (reducerKeys.length === 0) {
    return (
      'Store does not have a valid reducer. Make sure the argument passed ' +
      'to combineReducers is an object whose values are reducers.'
    );
  }

  if (!isPlainObject(inputState)) {
    return (
      `The ${argumentName} has unexpected type of "` +
      ({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
      `". Expected argument to be an object with the following ` +
      `keys: "${reducerKeys.join('", "')}"`
    );
  }

  var unexpectedKeys = Object.keys(inputState).filter(
    key => reducerKeys.indexOf(key) < 0
  );

  if (unexpectedKeys.length > 0) {
    return (
      `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
      `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
      `Expected to find one of the known reducer keys instead: ` +
      `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
    );
  }
}

//Check the legitimacy of reducer
// store = Redux.createStore(reducer, initialState) -->
// currentState = initialState
// currentState = currentReducer(currentState, action);
//
//From the perspective of call relationship and call timing, store Initial value of getstate() (currentstate)
//Is currentreducer (initialstate, {type: actiontypes. Init})
//
// 1.  In the initialization phase, the state value passed in by reducer is undefined. In this case, the initial state needs to be returned, and the initial state cannot be undefined
// 2.  When an unknown actiontype is passed in, the returned by reducer (state, {type}) cannot be undefined
// 3.  Redux / actions in this namespace should not be processed. Just return to currentstate directly (who is so unlucky to use this actiontype...)
function assertReducerSanity(reducers) {
  Object.keys(reducers).forEach(key => {
    var reducer = reducers[key];
    var initialState = reducer(undefined, { type: ActionTypes.INIT });

    if (typeof initialState === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
        `If the state passed to the reducer is undefined, you must ` +
        `explicitly return the initial state. The initial state may ` +
        `not be undefined.`
      );
    }

    var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.');
    if (typeof reducer(undefined, { type }) === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
        `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
        `namespace. They are considered private. Instead, you must return the ` +
        `current state for any unknown actions, unless it is undefined, ` +
        `in which case you must return the initial state, regardless of the ` +
        `action type. The initial state may not be undefined.`
      );
    }
  });
}

/**
 * Turns an object whose values are different reducer functions, into a single
 * reducer function. It will call every child reducer, and gather their results
 * into a single state object, whose keys correspond to the keys of the passed
 * reducer functions.
 *
 * @param {Object} reducers An object whose values correspond to different
 * reducer functions that need to be combined into one. One handy way to obtain
 * it is to use ES6 `import * as reducers` syntax. The reducers may never return
 * undefined for any action. Instead, they should return their initial state
 * if the state passed to them was undefined, and the current state for any
 * unrecognized action.
 *
 * @returns {Function} A reducer function that invokes every reducer inside the
 * passed object, and builds a state object with the same shape.
 */

export default function combineReducers(reducers) {
  //Returns an object, key = > value and value is function (in fact, filtering out non function)
  var finalReducers = pick(reducers, (val) => typeof val === 'function');
  var sanityError;

  try {
    //Make some legitimacy assertions for all child reducers. If there is no error, continue the following processing
    //See API notes for the content of legitimacy assertion
    assertReducerSanity(finalReducers);
  } catch (e) {
    sanityError = e;
  }

  //All key: value, set value to undefined, incomprehensible
  //In short, the initial state is something like {Hello: undefined, world: undefined}
  //Todo confirms the logic here
  var defaultState = mapValues(finalReducers, () => undefined);

  return function combination(state = defaultState, action) {
    if (sanityError) {
      throw sanityError;
    }

    var hasChanged = false;
    //This code, in short, is to cycle through finalstate [key] = FN (reducer, key)
    var finalState = mapValues(finalReducers, (reducer, key) => {
      var previousStateForKey = state[key];
      var nextStateForKey = reducer(previousStateForKey, action);
      if (typeof nextStateForKey === 'undefined') {
        //The other reducer returned undefined, so it hung up Throw error
        var errorMessage = getUndefinedStateErrorMessage(key, action);
        throw new Error(errorMessage);
      }
      //This code is a little confusing. From the design concept of Redux, except for the unknown action type, all other situations should return a new state
      //That is to say
      // 1.  The new type is "true" and "has" is "changed"
      // 2.  The action type is unknown, and the original state is returned, so here haschanged is false
      // 3.  Whether the action type is recognized or not, it is modified on the original state, but the modified state is returned (no copy is returned). Then, haschanged is still false
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
      return nextStateForKey;
    });

    //In the development environment (so remember to remove it in the production environment)
    //I'll study this code later. After all, it's not the main line
    if (process.env.NODE_ENV !== 'production') {
      var warningMessage = getUnexpectedStateKeyWarningMessage(state, finalState, action);
      if (warningMessage) {
        console.error(warningMessage);
      }
    }

    return hasChanged ? finalState : state;
  };
}

Source code analysis: bindactioncreator js

Although there are a lot of API comments, except for the legitimacy check, the key code is actually only a few sentences. Let’s look at a simple example first, which may be easier to understand. After reading it, you may feel that it is not the store The call of dispatch is handled conveniently…

var addTodo = function(text){
    return {
        type: 'add_todo',
        text: text
    };
};

var addTodos = function(){
    return {
        type: 'add_todos',
        items: Array.prototype.slice.call(arguments, 0)
    };
};

var reducer = function(state, action){
    switch (action.type) {
        case 'add_todo':
            return state.concat(action.text);
        case 'add_todos':
            return state.concat(action.items);
        default:
            return state;
    }
};


var store = redux.createStore(reducer, []);
//Note that the key code is here
var actions = redux.bindActionCreators({
    addTodo: addTodo,
    addTodos: addTodos
}, store.dispatch);

console.log('state is: ' + store.getState());

store. Dispatch ({type: 'add_todo', text: 'reading'});
store. Dispatch ({type: 'add_todos', items: [' reading ',' sleeping ']});
console. log('state is: ' + store.getState());  //  State is: reading, reading, sleeping

actions. Addtodo ('watching movies');
console. log('state is: ' + store.getState());  //  State is: reading, reading, sleeping,看电影

actions. Addtodos (['brush your teeth', 'take a bath']);
console. log('state is: ' + store.getState());  //  State is: reading, reading, sleeping,看电影,刷牙,洗澡

So, just look at the code. It’s very simple.

import mapValues from '../utils/mapValues';

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args));
}

/**
 * Turns an object whose values are action creators, into an object with the
 * same keys, but with every function wrapped into a `dispatch` call so they
 * may be invoked directly. This is just a convenience method, as you can call
 * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
 *
 * For convenience, you can also pass a single function as the first argument,
 * and get a function in return.
 *
 * @param {Function|Object} actionCreators An object whose values are action
 * creator functions. One handy way to obtain it is to use ES6 `import * as`
 * syntax. You may also pass a single function.
 *
 * @param {Function} dispatch The `dispatch` function available on your Redux
 * store.
 *
 * @returns {Function|Object} The object mimicking the original object, but with
 * every action creator wrapped into the `dispatch` call. If you passed a
 * function as `actionCreators`, the return value will also be a single
 * function.
 */
//Suppose actioncreators = = {addtodo: addtodo, removetodo: removetodo}
//In short, bind action creators (action creators, dispatch)
//The final return is:
// {
//   addTodo: function(text){
//      dispatch( actionCreators.addTodo(text) );
//   },
//   removeTodo: function(text){
//      dispatch( actionCreators.removeTodo(text) );
//   }
// }
//
//Or actioncreators = = = addtodo (addtodo is actioncreator)
//The last thing to return is
//  function() {
//     dispatch(actionCreators());
//  }
export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch);
  }

  if (typeof actionCreators !== 'object' || actionCreators === null || actionCreators === undefined) {  // eslint-disable-line no-eq-null
    throw new Error(
      `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
      `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    );
  }

  return mapValues(actionCreators, actionCreator =>
    bindActionCreator(actionCreator, dispatch)
  );
}

Source code analysis: applymiddleware js

Middleware should be the most important part of the Redux source code. Although there is a feeling of “ah ~ it’s just so” after understanding it, it was really confused at the beginning. The description of API, the preparation of middleware and the source code implementation of applymeddleware are not so easy to understand.

Before proceeding with the source code analysis, it is recommended to read the instructions for Middleware in the official document, link portal:http://camsong.github.io/redu…

Although the document is dead and long, I still have something to gain after reading it. Finally, I know that the implementation of applymeddleware is so convoluted…

Example: Redux thunk

Students who have used Redux to handle asynchronous requests should have used Redux thunk. Let’s take a look at his source code. It’s extremely short. Let alone your little partner, my little partner is stunned.

export default function thunkMiddleware({ dispatch, getState }) {
  return next => action =>
    typeof action === 'function' ?
      action(dispatch, getState) :
      next(action);
}

After that, when you look at the implementation of other middleware, they are basically the same. Let’s write a custom middleware, and you can basically see some ways.

'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = thunkMiddleware;
function thunkMiddleware(store) {
  var dispatch = store.dispatch;
  var getState = store.getState;

  return function (next) {
    return function (action) {
      return typeof action === 'function' ? action(dispatch, getState) : next(action);
    };
  };
}
module.exports = exports['default'];

Custom middleware: logger

Let’s first look at the implementation of logger

    function middleware(store){
        return function(next){
            return function(action){
                return next(action);
            }
        }
    }

Basically, we can see that the template of middleware declaration is coming, which is like this. Below combinationapplyMiddlewareTo describe the store, next and action parameters.

    function logger(store){
        return function(next){
            return function(action){
                console.log('logger: dispatching ' + action.type);
                var result = next(action);
                console.log('logger: next state ' + result);
                return result;
            }
        }
    }

Applymiddleware call example

The complete sample code is shown at the end of this section. You can see:

  1. The calling method of applymiddleware is applymiddleware (… Middleware) (react. Createstore). In fact, it’s easy to create a store first, and then apply middleware (… Middleware) (store) to achieve the same effect. However, the author deliberately designed this way to avoid multiple applications of the same middleware on the same store (seeOfficial document: try #6: use middleware “simply”
  2. The store parameter at the top level of middleware is not a conventional store, although it also has getstate and dispatch methods

    //In fact, the above parameter is the store object
        //Among them, the store is the internal store, and we are outside storewithmiddleware The internal implementation is converted to store dispatch
        //In addition, you can see the middlewareapi The dispatch method is the final encapsulated dispatch (please note that if store.dispatch is called inside the middleware, it may lead to an endless loop)
        var middlewareAPI = {
          getState: store.getState,
          //At the end, the dispatch method is overwritten and becomes the packaged dispatch method
          dispatch: (action) => dispatch(action)
        };
  3. Second floornextFunction is actually a “dispatch” method. Students familiar with express can probably guess its function. storeWithMiddleWare. When dispatching (action), it will enter each Middleware in order (according to the order of definition). From the current example, it’s about as follows. In fact, it’s coritization ~:

storeWithMiddleWare.dispatch(action) –> logger(store)(next)(action) –> timer(store)(next)(action) –> store.dispatch(action)

Complete sample code

function reducer(state, action){
        if(typeof state==='undefined') state = [];

        switch(action.type){
            case 'add_todo':
                return state.concat(action.text);
            default: 
                return state;
        }
    }
    
    function addTodo(text){
        return {
            type: 'add_todo',
            text: text
        };
    }

    //The store here is not redux Store from createstore (reducer, initialstate)
    //Instead, {getstate: store. Getstate, dispatch: function() {store. Dispatch (action);}}
    // 
    function logger(store){    
        //     
        return function(next){
            return function(action){
                console.log('logger: dispatching ' + action.type);
                var result = next(action);
                console.log('logger: next state ' + result);
                return result;
            }
        }
    }

    function timer(store){
        return function(next){
            return function(action){
                console.log('timer: dispatching ' + action.type);
                var result = next(action);
                console.log('timer: next state ' + result);
                return result;
            }
        }
    }

    var createStoreWidthMiddleware = redux.applyMiddleware(
        logger, 
        timer
        )(redux.createStore);
    
    var storeWithMiddleWare = createStoreWidthMiddleware(reducer);
    storeWithMiddleWare.subscribe(function(){
        console.log('subscribe: state is : ' + storeWithMiddleWare.getState());
    });
    console.log( storeWithMiddleWare.dispatch(addTodo('reading')) );

Source code analysis

Again, I suggest you look at it firstOfficial documentsThe introduction of middleware, otherwise it may be a little dizzy.

import compose from './compose';

/**
 * Creates a store enhancer that applies middleware to the dispatch method
 * of the Redux store. This is handy for a variety of tasks, such as expressing
 * asynchronous actions in a concise manner, or logging every action payload.
 *
 * See `redux-thunk` package as an example of the Redux middleware.
 *
 * Because middleware is potentially asynchronous, this should be the first
 * store enhancer in the composition chain.
 *
 * Note that each middleware will be given the `dispatch` and `getState` functions
 * as named arguments.
 *
 * @param {...Function} middlewares The middleware chain to be applied.
 * @returns {Function} A store enhancer applying the middleware.
 */
/*
  It can be seen from calling the method applymiddleware (... Middleware) (redux. Createstore)
  The next parameter is actually redux createStore.  And redux The calling method of createstore is redux createStore(reducer, initialState)
  So apply middleware (... Middleware)
  1. Parameter: redux createStore
  2. Return value: a function, followed by redux Createstore accepts the same parameters

 */
export default function applyMiddleware(...middlewares) {
  return (next) => (reducer, initialState) => {
    //Create a store internally first (equivalent to directly calling redux. Createstore (reducer, initialstate))
    var store = next(reducer, initialState);
    //Save the initial store dispatch
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      //At the end, the dispatch method is overwritten and becomes the packaged dispatch method
      dispatch: (action) => dispatch(action)
    };
    //Returns an array
    //Post an example here for reference, Redux thunk
    // function thunkMiddleware(store) {
    //  var dispatch = store.dispatch;
    //  var getState = store.getState;
    //
    //Next here is actually dispatch
    //  return function (next) {
    //    return function (action) {
    //      return typeof action === 'function' ? action(dispatch, getState) : next(action);
    //    };
    //  };
    //}
    /*
      Chain is an array. Referring to the middleware (Redux thunk) above, you can see that each element of chain is a function in the following form
      Also, the incoming store Getstate is the original store Getstate, and dispatch is the wrapped dispatch (not the original store. Dispatch)
      It seems to be to ensure that the original store is used when calling dispatch (action) in each middleware dispatch(action)
      Avoid store In the order of calling dispatch.middleware, the procedure of dispatch.middleware is overwritten The value of dispatch changes -- > store The values returned by dispatch may be different
      Against the design concept of Redux

      Next here is the original store Dispatch (see compose (... Chain) (store. Dispatch)
      function (next) {
        return function (action) {

        }
      }
     */
    chain = middlewares.map(middleware => middleware(middlewareAPI));

    //Compose (... Chain) (store. Dispatch) returns a function
    //The pseudo code is as follows:,
    // function (action) {
    //   middleware(store)(store.dispatch);
    // }
    dispatch = compose(...chain)(store.dispatch);  //  From right to left, middleware1 (middleware2 (middleware3 (dispatch)))

    //So, finally, applymiddleware (... Middleware) (redux. Createstore) is called
    //The returned store, getstate and subscribe methods are the original store getState, store. subscribe
    //As for dispatch, it is encapsulated
    return {
      ...store,
      dispatch
    };
  };
}

Related links

Official documents:http://camsong.github.io/redu…
Source code analysis GitHub address:https://github.com/chyingp/re…
Code examples related to source code analysis:https://github.com/chyingp/re…