preface

People who use React are familiar with Redux. Most React apps use it. When you use Redux, you don’t use it alone. You use it with React-Redux. When you first learn redux, it’s easy to confuse redux with React-Redux, thinking they’re the same thing. Redux is a predictable state container for javascript applications, and react-Redux is used to connect this state container to the React component. For example, in an ordinary family, mother is the supreme position in the family and controls the economic power of the family. The economic flow of the family passes through your mother, while your father is responsible for making money from outside and then gives it to your mother. Your mom is redux, your dad is React-Redux, and the react component is out there. Using this analogy, you get a sense of react and React-Redux. The react-Redux source code will be reviewed in my next article. If you guys think it’s good, please click on it!

The use of the story

Before talking about the source code for Redux, let’s review how redux is used, and then read the source code against the use of Redux, so that you can be more impressed. Post a demo code first:

const initialState={
  cash:200,

}
const reducer=(state=initialState,action)=>{
  const {type,payload} = action;
  switch(type) {case 'INCREMENT':
      return Object.assign({},state,{
        cash:state.cash+payload
      });
    case 'DECREMENT':
      return Object.assign({},state,{
        cash:state.cash-payload
      });
    default :
      returnstate; } } const reducers=Redux.combineReducers({treasury:reducer}); Const Store = redux.createstore (reducers); // Create a small Treasury const Store = redux.createstore (reducers); Store. Subscribe (()=>{console.log(' balance:${store.getState().treasury.cash}`); }); // Xiao Ming's father paid $300 to the store.dispatch({type:'INCREMENT', payload:300 }); // Xiao Ming pays the water and electricity bill with 100 yuan.type:'DECREMENT',
  payload:100
});
Copy the code

React-redux: React-Redux: React-Redux: React-Redux: React-Redux: React-Redux: React-Redux: React-Redux: React-Redux When we understand the use of Redux, look at the react-Redux source code to see why we wrote it that way and not this way in our project.

When it comes to the use of REdux, it is unavoidable to talk about the relationship between action, Reducer and store. I remember when I used Redux for the first time, I was always confused about the relationship between the three. I felt that the three were very abstract and metaphysical. I believe many friends have encountered the same situation as me. It’s not that hard, but I’m going to use the analogy that I started with and explain the relationship between these three things.

Now there is 200 dollars in the safe. To the end of the month, xiao Ming’s father’s unit sent a total of 300 dollars of wages, the first thing after getting wages is to hand over, no doubt, unless xiao Ming’s father is not dying. Can Xiao Ming’s father put the 300 dollars directly in the safe at home? Xiaoming’s dad has to submit an application for action. This is an action that includes the type of operation, INCREMENT, payload of 300 oceans. At this time, after Xiao Ming’s mother gets this application, she will perform corresponding operations according to this application, which is to put 300 dollars into the cash in the safe. At this time, Xiao Ming’s mother did what reducer did. When the 300 dollars were put out, Xiao Ming’s mother informed everyone in the family that the amount of the small Treasury had changed and the balance was now 500 dollars. When Xiao Ming’s father received the notice, it was a great relief. After a while, Xiao Ming came back with a bill for water and electricity worth 100 yuan. So, Xiaoming want xiaoming mother to apply for water and electricity bills, Xiaoming mother took out 100 pieces from the vault to Xiaoming, and informed everyone in the home that the amount of the small Treasury has changed, now the balance of 400 pieces.

Through the above example, I believe that boys have a clear understanding of the relationship between the three. Now that we have a clear understanding of the relationship between action, Reducer, and Store, and know how redux is used, we are going to start our source code reading.

Redux project structure

This article is based on the redux 4.0.0 version of the source code analysis, friends in the comparison of the source code, do not be mistaken. To read the source code for the entire Redux project, look no further than the SRC directory.

There are two main blocks, one is the custom tool library, and the other is the logic code of Redux. Which section shall we begin with? I personally recommend reading the section on custom libraries first. There are two main reasons. First, this piece of code is relatively simple and easy to understand, which makes it easier for people to read. Second, the redux logic code will use these custom tools, so understanding these first is a good preparation for reading the logic code later. Now we officially start our source code reading tour.

utils

actionTypes.js

const ActionTypes = {
  INIT:
    '@@redux/INIT' +
    Math.random()
      .toString(36)
      .substring(7)
      .split(' ')
      .join('. '),
  REPLACE:
    '@@redux/REPLACE' +
    Math.random()
      .toString(36)
      .substring(7)
      .split(' ')
      .join('. ')}export default ActionTypes
Copy the code

This code is easy to understand, just expose two action types, there is no difficulty. But I want to introduce here is Number. The prototype. The toString method, estimated that there should be a lot of friends do not know the toString can preach the ginseng, radix toString receives a parameter, represent Numbers of base, also is what we call 2 hexadecimal, decimal, hexadecimal, and so on. The radix range is also easy to work out, the least base is we get binary, so redix>=2. 0-9 (10 digits) +a-z (26 English letters) total 36, so Redix <=36. Summary: 2<=radix<=36, default is 10. We can write the length of a random string that gets the specified length:

// Gets a random string of the specified lengthfunction randomString(length){
  let str=' ';
  while(length>0){
    const fragment= Math.random().toString(36).substring(2);
    if(length>fragment.length){
      str+=fragment;
      length-=fragment.length;
    }else{ str+=fragment.substring(0,length); length=0; }}return str;
}

Copy the code

isPlainObject.js

export default function isPlainObject(obj) {
  if(typeof obj ! = ='object' || obj === null) return false

  let proto = obj
  while(Object.getPrototypeOf(proto) ! == null) { proto = Object.getPrototypeOf(proto) }return Object.getPrototypeOf(obj) === proto
}
Copy the code

Isplainobject.js is also very simple, simply exposing a function to determine whether a simple object is a simple object. What simple object? Prototype: __proto__ = Object. Prototype: __proto__ = Object. Prototype: __proto__ = Object.

Any Object that is not constructed as a new Object() or literal is not a simple Object

Here’s an example:

class Fruit{
  sayName(){
    console.log(this.name)
  }
}

class Apple extends Fruit{
  constructor(){
    super();
    this.name=The word "apple"
  }
}

const apple = new Apple();
const fruit = new Fruit();
const cherry = new Object({
  name:'cherry'
});
const banana = {
  name:'banana'}; console.log(isPlainObject(apple)); //falseconsole.log(isPlainObject(fruit)); //falseconsole.log(isPlainObject(cherry)); //trueconsole.log(isPlainObject(banana)); //true
Copy the code

IsPlainObject (fruit)===false isPlainObject(fruit)===false isPlainObject(fruit)===false

warning.js

export default function warning(message) {
  if(typeof console ! = ='undefined' && typeof console.error === 'function') {
    console.error(message)
  }
  try {
    throw new Error(message)
  } catch (e) {} 
}
Copy the code

This is also very simple, just print the error message. Console is not compatible with IE8 and below. Ah, not only sigh!

If Mosaic hindered the progress of human civilization, ie hindered the development of front-end technology.

Logic code

So far I have completed the JS analysis under Utils. It is very simple, not as difficult as you might think. Only from these a few simple JS, traction out a few we usually do not pay attention to the knowledge point. If we don’t read the source code, these easily overlooked knowledge points will be difficult to pick up, which is why many experts recommend reading the source code. Personally, I think reading the source code, understanding the principle is secondary. It’s more important to learn the big guy’s code style, some solutions, and light up your own blind spots. Without further ado, let’s move on to the next section of code reading, which is the core of redux.

index.js

import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'

function isCrushed() {}

if( process.env.NODE_ENV ! = ='production' &&
  typeof isCrushed.name === 'string'&& isCrushed.name ! = ='isCrushed'
) {
  warning(
    "You are currently using minified code outside of NODE_ENV === 'production'. " +
      'This means that you are running a slower development build of Redux. ' +
      'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
      'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' +
      'to ensure you have the correct code for your production build.')}export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

Copy the code

Index.js is the entry file of the whole redux, and the export method at the end is familiar. Each method corresponds to a JS, which is also what we will analyze later. There are two points about this:

The first one is __DO_NOT_USE__ActionTypes. This is very strange, we don’t use it very often in the project, the official redux documentation doesn’t mention this, if you don’t read the source code you probably don’t know it exists. What does this do? Bit by bit, we found this line of code:

import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
Copy the code

Isn’t this introduced JS a member of the utils we analyzed earlier? This variable is named to help developers check not to use redux’s built-in action types in case of errors.

The second, function isCrushed. There is a function isCrushed defined there, but there is nothing inside the function body. When I first saw it, I was like, why do you do that? I believe many friends have the same question as me, continue to read, followed by a code:

if( process.env.NODE_ENV ! = ='production' &&
  typeof isCrushed.name === 'string'&& isCrushed.name ! = ='isCrushed'
) {
  warning(
    "You are currently using minified code outside of NODE_ENV === 'production'. " +
      'This means that you are running a slower development build of Redux. ' +
      'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
      'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' +
      'to ensure you have the correct code for your production build.')}Copy the code

When we look at process.env.node_env, this is related to the environment variables we used to package. If process.env.node_env ===’production’ is not true, warning will not be executed. When the process. The env. NODE_ENV! Typeof isCrushed. Name === ‘string’ && isCrushed. Name! == ‘isCrushed’ will not be valid; When the process. The env. NODE_ENV! Name === ‘string’ && isCrushed. Name! === ‘string’ && isCrushed. == ‘isCrushed’ == ‘isCrushed’; Why is that not true? Function isCrushed function name will be replaced by a letter, here we take an example, I will redux project in the development environment for a compressed package. The code does this layer of transformation:

uncompressed

function isCrushed() {}
if( process.env.NODE_ENV ! = ='production' &&
  typeof isCrushed.name === 'string'&& isCrushed.name ! = ='isCrushed'
)

Copy the code

After the compression

function d() {}"string"==typeof d.name&&"isCrushed"! ==d.nameCopy the code

The judgment condition is true and the error message is printed. The main purpose of this is to prevent developers from compressing code in the development environment. Development environment compression code, not only let us

createStore.js

Function createStore accepts three parameters (Reducer, preloadedState, enhancer). Reducer and enhancer are mostly used by us, and preloadedState is seldom used. The first reducer is well understood and will not be explained here. The second preloadedState, which represents the initial state, is rarely used in our normal projects. Let’s talk about enhancer, which is called enhancer in Chinese and is designed to enhance Redux as its name implies. Createstore.js has this line of code:

 if(typeof enhancer ! = ='undefined') {
    if(typeof enhancer ! = ='function') {
      throw new Error('Expected the enhancer to be a function.')}return enhancer(createStore)(reducer, preloadedState)
  }
Copy the code

This line of code shows the enhancer call process, from which we can deduce that the enhancer function body shelf should look like this:

 function enhancer(createStore) {
    return(Reducer,preloadedState) => {// Logical code....... }}Copy the code

Common enhancers are Redux-thunk and redux-saga, and are often used in conjunction with applyMiddleware, which formats these enhancers into redux-specific enhancers. The implementation of applyMiddleware will be covered below. Let’s look at an example of redux-thunk:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';

const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);
Copy the code

After reading the above code, one might be wondering “Isn’t the second argument to the createStore function preloadedState? Won’t that give you an error?” First of all, there will be no error, after all, the official example, or write a wrong example is too big surprise! Redux must have done this. I found this line of code in createstore.js:

 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
Copy the code

If the second parameter preloadedState is of type Function and the third parameter enhancer is undefined, then preloadedState will be assigned to enhancer. PreloadedState will be undefined instead of enhancer. With this layer of transformation in place, we can boldly pass enhancer as the second parameter.

CreateStore: createStore: createStore: createStore: createStore: createStore: createStore: createStore The bottom line of createstore.js has this line of code:

return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
Copy the code

He returned several methods, of which the first three are the most commonly used, and the second two are rarely used in projects, and we will examine them one by one.

Some of the variables defined

letCurrentState = preloadedState // Obtained from the second preloadedState argument to createStoreletCurrentReducer = Reducer // Obtained from the first parameter of the createStore functionletCurrentListeners = [] // List of current subscribersletNextListeners = currentListeners // List of new subscriberslet isDispatching = false
Copy the code

Among them, the variable isDispatching is used as a lock. Our Redux is a unified managed state container, which needs to ensure the consistency of data, so we can only make one data modification at the same time. If two actions trigger the modification of the same data by the reducer at the same time, huge disasters will be caused. So the variable Isregularly exists to prevent this.

dispatch

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
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }
Copy the code

Function Dispatch makes three conditional judgments at the beginning of the function body, which are as follows:

  • Determine whether an action is a simple object
  • Check whether action.type exists
  • Check whether other reducer operations have been performed

Follow-up operations are performed only when the preceding three preset conditions are verified. Otherwise, an exception is thrown. Try -finally was used in the reducer operation. Maybe we usually use try-catch more, but this one is still less. Before executing isDispatching is set to true, preventing the subsequent actions from triggering the reducer operation, the state value obtained is assigned to currentState, and then isDispatching is changed to false in Finally. Allow subsequent actions to trigger the reducer operation. The subscriber is then notified one by one of the data updates, without passing in any parameters. Finally, the current action is returned.

getState

function getState() {
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.')}return currentState
  }
Copy the code

GetState is much simpler than Dispatch and returns currentState, which is updated with the response each time dispatch is performed. In order to ensure data consistency, the current state value cannot be read when reducer operation is performed. Speaking of which, I think of a previous interview experience:

Interviewer: Can I directly change the state of a store generated by createStore? Me: Yes. Interviewer: Do you know how Redux can't change store state? Me: well... Interviewer: That's easy! Rewrite the storesetMethods!Copy the code

I didn’t read redux’s source code, and he fooled me! After reading the redux source code, damn it! This guy is a liar! I have not read the source code also chat with me source code, speechless! Of course, I also have a reason, not good at art, was fooled. If we look at the source code, when getState returns state, we don’t have a copy of currentState, so we can change it directly. With this modification, subscribers will not be notified of the data update. The conclusions were:

The state derived by store from getState can be changed directly, but Redux does not allow this because it does not notify the subscriber to update the data.

subscribe

function subscribe(listener) {
    if(typeof listener ! = ='function') {
      throw new Error('Expected the listener to be a function.')}if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
          'If you would like to be notified after the store has been updated, subscribe from a ' +
          'component and invoke store.getState() in the callback to access the latest state. ' +
          'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.')}let isSubscribed = true// Indicates that the subscriber is in the subscription state,true- Subscription,falseTo unsubscribe ensureCanMutateNextListeners () nextListeners. Push (the listener)return function unsubscribe() {
      if(! isSubscribed) {return
      }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }
Copy the code

Before registering subscribers, two criteria are made:

  • Determines whether the listener is a function
  • Whether the reducer is modifying data (to ensure data consistency)

Function to be executed the next ensureCanMutateNextListeners, we look at the below ensureCanMutateNextListeners function of concrete realization logic:

 function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }
Copy the code

The logic is very simple to determine whether the nextListeners and currentListeners are the same reference. Remember the line of code in the Dispatch function and the variable definition?

// Function dispatch
const listeners = (currentListeners = nextListeners)
Copy the code
// Define variableslet currentListeners = []
let nextListeners = currentListeners
Copy the code

Both of these places refer to the same array of nextListeners and currentListeners, as well as the same code for defining variables. And ensureCanMutateNextListeners is used to judge the situation, when nextListeners and currentListeners for the same reference, then make a shallow copy, The array.prototype. slice method is used here, which returns a new Array so that a shallow copy can be achieved.

Function ensureCanMutateNextListeners as processing, adding new subscribers nextListeners, and return to unsubscribe unsubscribe function. When the unsubscribe function is executed, two conditions are also executed:

  • Whether the subscription has been unsubscribed (unsubscribed need not be executed)
  • Whether the reducer is modifying data (to ensure data consistency)

After judging the conditions, remove the subscriber from the nextListeners. Are there any currentListeners and nextListeners who may have questions about this process? The dispatch function merges the two into a single reference, why is there anything to separate them? Can’t we just use currentListeners? This is also done for data consistency, because there is a case. When Redux is notifying all subscribers, a new subscriber is added. Using only currentListeners can cause serious problems when new listeners jump in.

replaceReducer

  function replaceReducer(nextReducer) {
    if(typeof nextReducer ! = ='function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
  }
Copy the code

This function is used to replaceReducer, which is difficult to use in normal projects. A conditional judgment will be made before the replaceReducer function is executed:

  • Determine whether the reducer is a function

After judging the conditions, nextReducer is assigned to currentReducer to replace the reducer effect and trigger the state update operation.

observable

  /**
   * Interoperability point for observable/reactive libraries.
   * @returns {observable} A minimal observable of state changes.
   * For more information, see the observable proposal:
   * https://github.com/tc39/proposal-observable
   */
Copy the code

There’s no code here, because this is the code we don’t need to know. This Observable function, which is not called, can be used even if exposed. So let’s skip that and check out the Github address if you’re interested.


With these methods in mind, there is one more small detail that needs to be mentioned. There is a line of code in the createStore function body.

dispatch({ type: ActionTypes.INIT })
Copy the code

Why is there a line of code? The reason for this is simple, if we don’t have this code, currentState is undefined, so I’m saying we don’t have a default, so when we dispatch an action, we can’t update currentState. Therefore, we need to get all the default state of reducer, so that we can update our state when we dispatch an action later.

combineReducers.js

This JS corresponds to the combineReducers method in REdux and its main function is to merge multiple reducer methods. Now let’s give an empty function first, and then step by step restore the source code so that you can understand it more clearly.

// Reducers Object The corresponding value of each attribute should befunction
export default function combineReducers(reducers) {
    ....
}
Copy the code

Step 1: Shallow copy reducers

export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if(process.env.NODE_ENV ! = ='production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)}}if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)
}
Copy the code

A finalReducers and finalReducerKeys are defined to copy the Reducers and their attributes, respectively. Keys method was first used to get all the attributes of reducers, and then the for loop was conducted. Each reducer could be obtained according to its attributes and shallow copy was made into finalReducers, but the precondition was that the type of each reducer must be Function. Otherwise, the copy will be skipped.

Step 2: Check whether each reducer in finalReducers has a default return value

functionassertReducerShape(reducers) { Object.keys(reducers).forEach(key => { const reducer = reducers[key] const 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. If you don't want to set a value for this reducer, ` + `you can use null instead of undefined.` ) } const 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, but can be null.`
      )
    }
  })
}

export default functionCombineReducers (reducers) {// Omit the first step of the code......let shapeAssertionError
    try {
        assertReducerShape(finalReducers)
    } catch (e) {
        shapeAssertionError = e
    }
}
Copy the code

AssertReducerShape mainly detects two points:

  • Cannot occupy the

    namespace
  • If you encounter an unknown action type, you do not need to use the default return value

If you pass in an action whose type is @@redux/INIT< random value > and return undefined, you are not responding to the unknown action type and need to add the default value. If the action of type @@redux/INIT< random value > is not undefined, but the action of type @@redux/PROBE_UNKNOWN_ACTION_< random value > is undefined. Note The

namespace is occupied. The whole logic is relatively simple, take a good comb yourself.

Step 3: Return a function to broker all the reducer


export default functionCombineReducers (reducers) {// Omit the code of step 1 and step 2......let unexpectedKeyCache
        if(process.env.NODE_ENV ! = ='production') {
        unexpectedKeyCache = {}
    }
    return function combination(state = {}, action) {
        if (shapeAssertionError) {
            throw shapeAssertionError
        }

        if(process.env.NODE_ENV ! = ='production') {
            const warningMessage = getUnexpectedStateShapeWarningMessage(
                state,
                finalReducers,
                action,
                unexpectedKeyCache
            )
            if (warningMessage) {
                warning(warningMessage)
            }
        }

        let hasChanged = false
        const nextState = {}
        for (let i = 0; i < finalReducerKeys.length; i++) {
            const key = finalReducerKeys[i]
            const reducer = finalReducers[key]
            const previousStateForKey = state[key]
            const nextStateForKey = reducer(previousStateForKey, action)
            if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey ! == previousStateForKey }return hasChanged ? nextState : state
    }    
}
Copy the code

First of all the incoming getUnexpectedStateShapeWarningMessage state made a anomaly detection, find out the state does not have corresponding key of reducer, and prompts the developer to do adjustment. Then we jump getUnexpectedStateShapeWarningMessage, see its implementation.

function getUnexpectedStateShapeWarningMessage(
  inputState,
  reducers,
  action,
  unexpectedKeyCache
) {
  const reducerKeys = Object.keys(reducers)
  const argumentName =
    action && action.type === ActionTypes.INIT
      ? 'preloadedState 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('", "')}"` ) } const unexpectedKeys = Object.keys(inputState).filter( key => ! reducers.hasOwnProperty(key) && ! unexpectedKeyCache[key] ) unexpectedKeys.forEach(key => { unexpectedKeyCache[key] =true
  })

  if (action && action.type === ActionTypes.REPLACE) return

  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.`
    )
  }
}
Copy the code

GetUnexpectedStateShapeWarningMessage receive four parameters InputState (state), Reducers (finalReducers), Action (Action), unexpectedKeyCache (unexpectedKeyCache), It should be noted here that unexpectedKeyCache is a collection from the last check of inputState that does not carry the corresponding abnormal keys from the Reducer collection. The whole logic is as follows:

  1. Preconditions are judged to ensure that reducers collection is not {} and inputState is a simple object
  2. Find the keys that are in inputState but not in the Reducers collection
  3. If the reducer action is to be replaced, skip step 4 and no exception information is printed
  4. Print out all abnormal keys

GetUnexpectedStateShapeWarningMessage after analysis, we then look at the back of the code.

    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey ! == previousStateForKey }return hasChanged ? nextState : state
Copy the code

Firstly, a hasChanged variable was defined to indicate whether the state had changed, and the reducers set was traversed. The original state corresponding to each Reducer was introduced into it, and the corresponding new state was obtained. Facing new state do after then a layer of undefined check, function getUndefinedStateErrorMessage code is as follows:

function getUndefinedStateErrorMessage(key, action) {
  const actionType = action && action.type
  const actionDescription =
    (actionType && `action "${String(actionType)}"`) | |'an action'

  return (
    `Given ${actionDescription}, reducer "${key}" returned undefined. ` +
    `To ignore an action, you must explicitly return the previous state. ` +
    `If you want this reducer to hold no value, you can return null instead of undefined.`
  )
}
Copy the code

The logic is simple, just a concatenation of error messages. After undefined verification, it will be compared with the original state to determine whether it has changed. NextState is returned if the change occurs, or state is returned otherwise.

compose.js

The main function is to connect multiple functions, the return value of one function as another function to calculate the final return value. Take cooking as an example, every dish is made from the original ingredients through one process after another. Compose connects these processes together by simply providing the ingredients, and the compose app will automatically help you through process after process.

export default functioncompose(... funcs) {if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  returnfuncs.reduce((a, b) => (... args) => a(b(... args))) }Copy the code

The above is the ES6 code, which may not be well understood by your friends. In order to facilitate your understanding, I will convert it into ES5 code to explain.

function compose() {
  var _len = arguments.length;
  var funcs = [];
  for (var i = 0; i < _len; i++) {
    funcs[i] = arguments[i];
  }

  if (funcs.length === 0) {
    return function (arg) {
      return arg;
    };
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce(function (a, b) {
    return function () {
      return a(b.apply(undefined, arguments));
    };
  });
}
Copy the code

Comb through the whole process, roughly divided into the following steps:

  1. Create a new array funcs and copy each item from arguments into Funcs
  2. When funcs has length 0, returns a function that returns what is passed in
  3. When funcs has length 1, return the function corresponding to the 0th term of funcs
  4. When funcs has a length greater than 1, the array.prototype. reduce method is called for consolidation

Here we just review the reduce method of arrays. The reduce function takes the following four arguments

  • Total Initial value or calculated return value
  • Current Current element
  • Index Indicates the index of the current element
  • Array Array of the current element

Example:

Const array =,2,3,4,5,6,7,8,9,10 [1]; const totalValue=array.reduce((total,current)=>{returntotal+current }); / / 55Copy the code

Compose does not execute from left to right. Instead, it executes from right to left. Here’s an example:

const value=compose(function(value){
  return value+1;
},function(value){
  return value*2;
},function(value){
  returnvalue-3; }) (2); console.log(value); / / (2-3) * 2 + 1 = 1Copy the code

If you want it to execute from left to right, it’s easy to reverse the order.

= = = > before the conversionreturna(b.apply(undefined, arguments)); = = = > after conversionreturn b(a.apply(undefined, arguments));
Copy the code

applyMiddleware.js

export default functionapplyMiddleware(... middlewares) {returncreateStore => (... args) => { const store = createStore(... args)let dispatch = () => {
      throw new Error(
        `Dispatching whileconstructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) } const middlewareAPI = { getState: store.getState, dispatch: (... args) => dispatch(... args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(... chain)(store.dispatch)return {
      ...store,
      dispatch
    }
  }
}
Copy the code

We mentioned applyMiddleware earlier in enhancer, so let’s compare the two formats.

// enhancer
 function enhancer(createStore) {
    return(Reducer,preloadedState) => {// Logical code....... } } //applyMiddlewarefunction//applyMiddleware(... middlewares) {returncreateStore => (... Args) => {// Logical code....... }}Copy the code

The function applyMiddleware returns an enhancer.

  1. Create a store using the createStore method
  2. Specify a dispatch that throws an error message if called during middleware construction
  3. Define the middlewareAPI with two methods, getState and Dispatch, as a bridge to the store called by the middleware
  4. Middlewares calls array.prototype. map and stores it in chain
  5. Compose consolidates the chain array and assigns the value to dispatch
  6. Replace the original store.dispatch with the new dispatch

See the whole process may be friends or confused, metaphysical very! Redux-thunk redux-thunk redux-thunk redux-thunk

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;
Copy the code

Ha ha ha! After reading the redux-Thunk source code is not very crashed, thousands of star project incredibly a few lines of code, suddenly three views on the destruction of any? In fact, the source code is not as complex as you imagine, do not listen to the source code panic. Hold! We can win! Redux-thunk redux-Thunk redux-Thunk

 const thunk = ({ dispatch, getState })=>{
    return next => action => {
        if (typeof action === 'function') {
            return action(dispatch, getState);
        }
        return next(action);
    };
 }  
Copy the code

By step 4 of applyMiddleware, the chain array should look something like this:

const newDispatch; const middlewareAPI={ getState:store.getState, dispatch: (... args) => newDispatch(... args) } const { dispatch, getState } = middlewareAPI; const fun1 = (next)=>{return action => {
    if (typeof action === 'function') {
        return action(dispatch, getState);
    }
    return next(action);
  }
}
const chain = [fun1]
Copy the code

Compose’s new dispatch from the chain array should look like this:

const newDispatch; const middlewareAPI={ getState:store.getState, dispatch: (... args) => newDispatch(... args) } const { dispatch, getState } = middlewareAPI; const next = store.dispatch; newDispatch = action =>{if (typeof action === 'function') {
    return action(dispatch, getState);
  }
  return next(action);
}
Copy the code

We can then use the redux-Thunk example to simulate the whole process:

function makeASandwichWithSecretSauce(forPerson) {
  return function (dispatch) {
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop'.forPerson, error)) ); }; } / / store. The dispatch is equivalent to the newDispatch store. Dispatch (makeASandwichWithSecretSauce ('Me') ====> convert constforPerson = 'Me';
const action = (dispatch)=>{
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop'.forPerson, error))
    );
}
newDispatch()

===> typeof action === 'function'When it was formed (= > {(dispatch)return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop'.forPerson, error)) ); }) ((... args) => newDispatch(... Args), getState) ====> Compute run result constforPerson = 'Me'; const dispatch = (... args) => newDispatch(... The args); fetchSecretSauce().then( sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop'.forPerson, error)) ); / / which:function fetchSecretSauce() {
  return fetch('https://www.google.com/search?q=secret+sauce');
}
function makeASandwich(forPerson, secretSauce) {
  return {
    type: 'MAKE_SANDWICH'.forPerson,
    secretSauce
  };
}

function apologize(fromPerson, toPerson, error) {
  return {
    type: 'APOLOGIZE', fromPerson, toPerson, error }; } ====> We only calculate the result of promise.resolve here, and assume that fetchSecretSauce returns a value of'666', namely the sauce ='666'

const forPerson = 'Me'; const dispatch = (... args) => newDispatch(... The args); dispatch({type: 'MAKE_SANDWICH'.'Me'.'666'}) ====> Convert const action = {type: 'MAKE_SANDWICH'.'Me'.'666'
};

const next = store.dispatch

const newDispatch = action =>{
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }
  returnnext(action); } newDispatch(action) ====> Final result store.dispatch({type: 'MAKE_SANDWICH'.'Me'.'666'
});
Copy the code

Redux-thunk Redux-Thunk Redux-Thunk Redux-Thunk Redux-Thunk

bindActionCreators.js

export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  if(typeof actionCreators ! = ='object' || actionCreators === 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"? ` ) } const keys = Object.keys(actionCreators) const boundActionCreators = {}for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
Copy the code

BindActionCreators has three return values for the three scenarios, and we will analyze them based on the return values of each case. (For ease of understanding, we chose the case without integrated middleware)

typeof actionCreators === ‘function’

function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}
const actionFun=bindActionCreator(actionCreators, Dispatch) ===> Const fun1 = actionCreators; const dispatch= stror.dispatch; const actionFun=function () {
    return dispatch(fun1.apply(this, arguments))
 }
Copy the code

According to the deduction above, when the variable actionCreators is of type Function, the actionCreators must return an action.

typeof actionCreators ! == ‘object’ || actionCreators === 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"?`
    )
Copy the code

Indicates to the developer that the actionCreators type is incorrect and should be a non-empty object or function.

The default

 const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
Copy the code

In comparison with the first case, each of the actionCreators entries performed the same operation. In other words, the default is a set of the first.


This is an explanation of bindActionCreators, which may not be understandable to you, but it doesn’t matter. Just know what bindActionCreators did. BindActionCreators needs to be used in conjunction with React-Redux. Since this article does not cover React-Redux, we will not provide a more in-depth explanation of bindActionCreators. In the next article, react-Redux, we’ll revisit Creators.

conclusion

Here the whole redux source code we have been analyzed, the whole redux code is not very large, but there are still a lot of things, relatively a little logic around. But that’s okay, there’s nothing that you can’t understand after watching it a few times. If you can, just watch it a few more times. In addition, one more mouth, if you want to read fast to improve their partners, I personally is strongly recommended to see the source code. Is the so-called “close to the red, close to the black”, look at the code of god, on their own code writing, code logic, knowledge points to check gaps and so on are very helpful. Take myself as an example, I read a piece of source code after each benefit. May be the first time to see the source code, there are many not to adapt to, after all, everything is difficult before it begins, if you force yourself to complete the first source code reading, the future source code reading will be more and more relaxed, for their own promotion will be faster and faster. Ladies and gentlemen, roll up your sleeves and come on!