Redux

Read the redux source code

RandomString generates a randomString

var randomString = function randomString() {
  return Math.random().toString(36).substring(7).split(' ').join('. ');
};
Copy the code

Number. The prototype. The toString () : the toString () method returns a string representation of the specified Number object.

numObj.toString([radix])

  • If the RADIX parameter is not specified, the default value is 10;
  • If the Radix parameter is not between 2-36, a RangeError will be raised
  • If the base of the conversion is greater than 10, letters are used for numbers greater than 9, for example, if the base is 16, letters A through F are used for 10 to 15.
A recent question that comes to mind [1.2.3.2].map(parseInt) // [1, NaN, NaN, 2]
Copy the code

IsPlainObject Determines whether a function is stored

function isPlainObject(obj) {
  if (typeofobj ! = ='object' || obj === null) return false;
  var proto = obj;

  while (Object.getPrototypeOf(proto) ! = =null) {
    proto = Object.getPrototypeOf(proto);
  }

  return Object.getPrototypeOf(obj) === proto;
}
Copy the code

Why does it have to be so complicated?

  • Obj may be generated from other runtime environments (such as syndomain IFrame in browser, VM in NodeJS)
  • Of course, it is possible to make the code behave differently
let obj = Object.create(Object.create(null));
// Write it literally like this:
let obj = {
    __proto__: {
        __proto__: null}}; isPlainObject(obj)// true
isPlainObject(null) // false
Copy the code

CreateStore creates a Redux state tree and the return value is a Store

  • Reducer is a function that accepts the current state and the actions to be processed and returns a new state
  • PreloadedState Initialization state
  • Enhancer Store enhancer
function createStore(reducer, preloadedState, enhancer) {
  var _ref2;

  If preloadedState is a function and enhancer is a function, or if enhancer and the fourth argument are functions, then an error is thrown
  if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3= = ='function') {
    throw new Error('It looks like you are passing several store enhancers to ' + 'createStore(). This is not supported. Instead, compose them ' + 'together to a single function.');
  }

  // If preloadedState is a function and the enhancer is not passed, the function is assigned as an enhancer and the enhancer initialization state is assigned as undefined
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState;
    preloadedState = undefined;
  }

  If the enhancer is not empty but not a function, an error is thrown
  if (typeofenhancer ! = ='undefined') {
    if (typeofenhancer ! = ='function') {
      throw new Error('Expected the enhancer to be a function.');
    }

    Serv (serv); serv (serv); serv (serV); serv (serv); serv (serv); serv (serv)
    return enhancer(createStore)(reducer, preloadedState);
  }

  If reducer is not a function, it must be a function
  if (typeofreducer ! = ='function') {
    throw new Error('Expected the reducer to be a function.');
  }

  / / the current reducer
  var currentReducer = reducer;
  / / the current state
  var currentState = preloadedState;
  // The current listener
  var currentListeners = [];
  var nextListeners = currentListeners;
  var isDispatching = false;

  Implement a shallow copy of currentListeners
  function ensureCanMutateNextListeners() {
    if(nextListeners === currentListeners) { nextListeners = currentListeners.slice(); }}// Get the state of the current application, not at dispatch, otherwise an error will be reported
  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;
  }

  // Add a listener that will be called every time the subscription is dispatched. The return value is a function that can be used to close the subscription
  function subscribe(listener) {
    // The subscription function must be a function that can be called
    if (typeoflistener ! = ='function') {
      throw new Error('Expected the listener to be a function.');
    }

    // An error is thrown if the call is still made from dispatch
    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#subscribelistener for more details.');
    }

    var isSubscribed = true;
    Shallow copy currentListener to nextListeners
    ensureCanMutateNextListeners();
    // Then add the new listener to the end of the shallow copy
    nextListeners.push(listener);

    // This is the return value unsubscribe
    return function unsubscribe() {
      // Return if the subscription has already been unsubscribed
      if(! isSubscribed) {return;
      }

      // As above, never do anything at dispatch
      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#subscribelistener for more details.');
      }

      // The tag has been unsubscribed. This is a closure function that gets the parameters accessible for each call. The judgment of the isSubscribed
      isSubscribed = false;
      // A snapshot of the current is generated at dispatch
      ensureCanMutateNextListeners();
      var index = nextListeners.indexOf(listener);
      // Find and delete
      nextListeners.splice(index, 1);

      // TODO:Then empty the currentListeners
      currentListeners = null;
    };
  }

  // The only way to change state for an action is if an object with no side effects returns the action passed in
  function dispatch(action) {
    // Check whether the action is present or not
    if(! isPlainObject(action)) {throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
    }

    // Specify that the action must have the type attribute and cannot be undefined
    if (typeof action.type === 'undefined') {
      throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant? ');
    }

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

    try {
      isDispatching = true;
      // currentReducer (reducer) accepts two parameters. CurrentState currentState may also be undefined
      currentState = currentReducer(currentState, action);
    } finally {
      // When the reducer is complete, isDispatching becomes false
      isDispatching = false;
    }

    // A snapshot of the latest listener is generated after each unsubscribe shallow copy
    var listeners = currentListeners = nextListeners;

    for (var i = 0; i < listeners.length; i++) {
      var listener = listeners[i];
      listener();
    }

    return action;
  }
  
  // Replace reducer and change state returns null
  function replaceReducer(nextReducer) {
    The reducer is not a function
    if (typeofnextReducer ! = ='function') {
      throw new Error('Expected the nextReducer to be a function.');
    }

    currentReducer = nextReducer;

    // Initiate a replace action
    dispatch({
      type: ActionTypes.REPLACE
    });
  }
  

  function observable() {
    var _ref;

    // The subscribe function defined above takes a listener as an argument and returns a function that removes the current listener
    var outerSubscribe = subscribe;
    return _ref = {
      // A subscribe function takes an object with the next attribute and returns an object with the unsubscribede
      subscribe: function subscribe(observer) {
        // An error is reported if the observer is not an object
        if (typeofobserver ! = ='object' || observer === null) {
          throw new TypeError('Expected the observer to be an object.');
        }

        // https://github.com/tc39/proposal-observable
        function observeState() {
          if (observer.next) {
            // The current state is passed inobserver.next(getState()); }}// Initialize once
        observeState();
        // Register observeState listener
        var unsubscribe = outerSubscribe(observeState);
        return {
          unsubscribe: unsubscribe
        };
      }
    }, _ref[$$observable] = function () {
      // TODO
      return this;
    }, _ref; // this is a comma expression that returns _ref
  } 

  Create an initial state from reducer when a store is created
  dispatch({
    type: ActionTypes.INIT
  });

  The _ref2 store contains a dispatch/subscribe/getState/replaceReducer and an Observable
  return _ref2 = {
    dispatch: dispatch,
    subscribe: subscribe,
    getState: getState,
    replaceReducer: replaceReducer
  }, _ref2[$$observable] = observable, _ref2;
}
Copy the code

Warning Displays warning information on the console

  function warning(message) {
    // It is a good practice to determine whether console exists in a different context before invoking it
    if (typeof console! = ='undefined' && typeof console.error === 'function') {
      console.error(message);
    }

    try {
      throw new Error(message);
    } catch (e) {} 

  }
Copy the code

When you return to undefined getUndefinedStateErrorMessage when reducer through the action of the error message

function getUndefinedStateErrorMessage(key, action) {
  var actionType = action && action.type;
  var 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

Warned getUnexpectedStateShapeWarningMessage by comparing the state and the attribute of reducer

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

  // Do not have a reducer, otherwise an error is reported
  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 the state is not stored, the error is reported
  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(function (key) {
    return! reducers.hasOwnProperty(key) && ! unexpectedKeyCache[key]; }); unexpectedKeys.forEach(function (key) {
    unexpectedKeyCache[key] = true;
  });
  / / REPLACE
  if (action && action.type === ActionTypes.REPLACE) return;

  // Otherwise, there must be a reducer for each state's attributes
  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

AssertReducerShape determines whether reducers are legitimate

function assertReducerShape(reducers) {
  Object.keys(reducers).forEach(function (key) {
    var reducer = reducers[key];
    var initialState = reducer(undefined, {
      type: ActionTypes.INIT
    });

    // An error is reported if state is returned during initialization or when state is given to undefined
    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.");
    }

    // It must be able to catch an unknown action with default returning the current state or an error will be reported
    if (typeof reducer(undefined, {
      type: ActionTypes.PROBE_UNKNOWN_ACTION()
    }) === '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."); }}); }Copy the code

CombineReducers integrate reducers into a single Reducer/return value is a function that receives the initial state and receives an action that returns the changed state

function combineReducers(reducers) {
  var reducerKeys = Object.keys(reducers);
  var finalReducers = {};

  // Go through the reducer
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i];

    if(process.env.NODE_ENV ! = ='production') {
      // A warning message will be printed on the console if it is not in production
      if (typeof reducers[key] === 'undefined') {
        warning("No reducer provided for key \"" + key + "\" "); }}// By judging whether the function is the final reducers formed, it can also be removed to prevent the passed reducers from having duplicate keys
    if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key]; }}var finalReducerKeys = Object.keys(finalReducers); 

  var unexpectedKeyCache;

  if(process.env.NODE_ENV ! = ='production') {
    unexpectedKeyCache = {};
  }

  var shapeAssertionError;

  try {
    // Determine whether reducers are legal
    assertReducerShape(finalReducers);
  } catch (e) {
    shapeAssertionError = e;
  }

  // The return value is a function that accepts a state and an action
  return function combination(state, action) {
    // If state is not passed, an empty object is passed
    if (state === void 0) {
      state = {};
    }

    If the reducer does not meet the specification, the reducer should reduce the error
    if (shapeAssertionError) {
      throw shapeAssertionError;
    }

    if(process.env.NODE_ENV ! = ='production') {
      // If the reducer is not a production environment, check whether the incoming state has keys. No reducer is available
      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);

      if(warningMessage) { warning(warningMessage); }}var hasChanged = false;
    var nextState = {};

    // Walk through the final reducers
    for (var _i = 0; _i < finalReducerKeys.length; _i++) {
      var _key = finalReducerKeys[_i];
      var reducer = finalReducers[_key];
      var previousStateForKey = state[_key];
      // previousStateForKey may be undefined so reducer must have a default state
      var nextStateForKey = reducer(previousStateForKey, action);

      if (typeof nextStateForKey === 'undefined') {
        // An error is reported if the default state is not given
        var errorMessage = getUndefinedStateErrorMessage(_key, action);
        throw new Error(errorMessage);
      }

      nextState[_key] = nextStateForKey;
      // Check whether state changes by comparinghasChanged = hasChanged || nextStateForKey ! == previousStateForKey; } hasChanged = hasChanged || finalReducerKeys.length ! = =Object.keys(state).length;
    // If the reducer changes the state values or the reducer and state values do not match, the latest state is returned => nextState
    return hasChanged ? nextState : state;
  };
}
Copy the code

BindActionCreator takes an actionCreator function and the second argument is Dispatch

function bindActionCreator(actionCreator, dispatch) {
  // Returns a function dispatch an action
  return function () {
    return dispatch(actionCreator.apply(this.arguments));
  };
}
Copy the code

The first parameter of bindActionCreators can be either function or an object whose value is actionCreator and the second parameter is Dispatch and the return value is the function that uses Dispatch or the object whose value is creators

function bindActionCreators(actionCreators, dispatch) {
  // If the first argument is a function, return the function wrapped by Dispatch
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch);
  }

  // An error is thrown if the object is not excluded to null
  if (typeofactionCreators ! = ='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\"?");
  }

  var boundActionCreators = {};

  for (var key in actionCreators) {
    // hasOwnProperty
    var actionCreator = actionCreators[key];

    if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch); }}// The return value is one function for each function wrapped in dispatch
  return boundActionCreators;
}
Copy the code

_defineProperty Object. function of defineProperty

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true.configurable: true.writable: true
    });
  } else {
    obj[key] = value;
  }

  return obj;
}
Copy the code

OwnKeys takes an argument and an enumerable argument returns all keys

function ownKeys(object, enumerableOnly) {
  // All keys on the object but not the symbol key
  var keys = Object.keys(object);

  // Add the Symbol key
  if (Object.getOwnPropertySymbols) {
    keys.push.apply(keys, Object.getOwnPropertySymbols(object));
  }

  if (enumerableOnly) keys = keys.filter(function (sym) {
    Accessor properties
    return Object.getOwnPropertyDescriptor(object, sym).enumerable;
  });
  return keys;
}
Copy the code

_objectSpread2

function _objectSpread2(target) {
  // If none of the arguments are returned directly
  for (var i = 1; i < arguments.length; i++) {
    / / can't be null because can't use the Object to null or undefined. GetOwnPropertyDescriptors
    var source = arguments[i] ! =null ? arguments[i] : {};

    // Aggregate all parameters into an Object
    if (i % 2) {
      ownKeys(source, true).forEach(function (key) {
        _defineProperty(target, key, source[key]);
      });
    } else if (Object.getOwnPropertyDescriptors) {
      Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
    } else {
      ownKeys(source).forEach(function (key) {
        Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); }}return target;
}
Copy the code

Compose Currified (… args) => f(g(h(… Args))) are executed from right to left

function compose() {
  // Iterate over the parameters to generate an array of all parameters
  for (var _len = arguments.length, funcs = new Array(_len), _key = 0; _key < _len; _key++) {
    funcs[_key] = arguments[_key];
  }

  // Generate a function if there are no arguments
  if (funcs.length === 0) {
    return function (arg) {
      return arg;
    };
  }

  // Return the current function if there is only one function
  if (funcs.length === 1) {
    return funcs[0];
  }

  // A reduce function is wrapped from left to right with the last one at the bottom
  return funcs.reduce(function (a, b) {
    return function () {
      return a(b.apply(void 0.arguments));
    };
  });
}
Copy the code

ApplyMiddleware takes some middleware and generates a complete enhancer

function applyMiddleware() {
  // Same as above to an array of middleware
  for (var _len = arguments.length, middlewares = new Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  // Returns a function that takes createStore
  return function (createStore) {
    return function () {

      // Generate a store
      var store = createStore.apply(void 0.arguments);

      // Cannot be dispatched while building middleware because the rest of the middleware will not receive the dispatch if it is called
      var _dispatch = function dispatch() {
        throw new Error('Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.');
      };

      // Pass in an error-reporting dispatch to prevent dispatch from being called while building middleware
      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch() {
          return _dispatch.apply(void 0.arguments); }};// Generate an array of middleware
      var chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      {getState, dispatch} returns a function that receives a dispatch
        = compose.apply(void 0, chain)(store.dispatch);
      // Generate an enhanced store object
      return _objectSpread2({}, store, {
        dispatch: _dispatch
      });
    };
  };
}
Copy the code

IsCrushed false function

function isCrushed() {}

// An alarm is generated if the function name is compressed in a non-production environment
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 setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' + 'to ensure you have the correct code for your production build.');
}
Copy the code

That’s all the Redux code

Here is an example to reinforce it again

import { createStore, applyMiddleware, combineReducers, compose } from 'redux';
import reducers from './reducers';

// Suppose there are two middleware components
import thunk from 'redux-thunk';

// https://github.com/fcomb/redux-logger.git
const logMiddleware = ({ getState }) = > dispatch= > {
  return action= > {
    const current prevState = getState()
    const returnValue = dispatch(action)
    const nextValue = getState()


    if (typeof console! = ='undefined') {
      const message = `action ${action.type} @ ${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`;

      try {
        console.group(message);
      } catch(e) {
        console.log('NOT GROUP');
      }

      console.log(`%c prev state`.`color: #9E9E9E; font-weight: bold`, prevState);
      console.log(`%c action`.`color: #03A9F4; font-weight: bold`, action);
      console.log(`%c next state`.`color: #4CAF50; font-weight: bold`, nextState);

      try {
        console.groupEnd('- the log end -);
      } catch(e) {
        console.log('- the log end -); }}// The return value is action if it does not change
    returnreturnValue; }}// Generate middleware collection
const createStoreWithMiddleware = applyMiddleware(logger, thunk)(createStore);
/ / generated reducer
const reducer = combineReducers(reducers);
// Then generate the global store
const store = createStoreWithMiddleware(reducer);
Copy the code