CreateStore and combineReducers

Story ideas

Redux is a Javascript state container that provides predictable state management. For an application with a complex point, it is difficult to manage its constantly changing states. If the modification of states is not restricted, the states will affect each other. So we can’t predict what happens when an operation is triggered.

Single data source

The state of the entire application exists in a globally unique Store, which is a tree object that contains all the states in the application. Each component uses a portion of the data from the tree object.

Keep status read-only

Store state cannot be changed directly. To change the state of an application, dispatch must send an Action object.

Data modification is done using pure functions.

The pure function here is reducer(state, action), the first parameter state is the current state, the second parameter action is the object used for dispatch, and the new state value returned by the function is completely determined according to the input parameter value.

createStore

The Redux library doesn’t have many exposed properties, and createStore is one of the most important, which is used to generate our app’s store. Store provides users with the following properties

// Remove the enhancer part of the source createStore
function createStore(reducer, preloadedState) {
  var currentReducer = reducer;/ / reducer function
  var currentState = preloadedState;/ / the default state
  var currentListeners = []; // The current listener function
  var nextListeners = currentListeners;
  var isDispatching = false; // Whether the application is currently performing distribution operations

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      // Make a shallow copy of the array. Create a new array that has nothing to do with the previous arraynextListeners = currentListeners.slice(); }}function getState() {}
  function dispatch(action) {}
  function subscribe(listener) {}
  
  // Used to generate the default state for the application
  dispatch({ type: ActionTypes.INIT }); 
}
Copy the code
  1. GetState () uses closure technology to get the application state

     function getState() {
        if(isDispatching) {... }State cannot be obtained during reducer execution
        return currentState; // Get the local variable currentState defined inside the function outside the function using the closure technique
     }
    Copy the code
  2. Dispatch () receives an action object that must contain the Type attribute as a parameter to dispatch events and calls the Reducer function to return a new state

    function dispatch(action) {
      	// Action must be an object
        if(! isPlainObject(action)) { ... }// The action object must contain the type attribute
        if (typeof action.type === 'undefined') {... }// No dispatch operation is currently in progress
        if (isDispatching) { ... } 
        
        try {
          isDispatching = true; 
          // Call Reducer to return a new state, where the new state value depends entirely on the input parameters currentState and Action. Therefore, reducer needs to be a pure function so that the state changes can be predictable and traceable.
          currentState = currentReducer(currentState, action); 
        } finally {
          isDispatching = false; // The reducer function is finished, and the reducer function status is changed
        }
    		// There are two currentListeners and nextListeners that assign values to each other. The other is where the createStore function was originally executed. We'll explain why two variables are needed to maintain the process
        var listeners = currentListeners = nextListeners;
         // Iterate over the listener functions and execute them in turn to notify the listener of a change in page state
        for (var i = 0; i < listeners.length; i++) {
          var listener = listeners[i];
          listener();
        }
        return action;
     }
    Copy the code
  3. The subscribe() function, which receives a callback function as an argument, notifies the component of a status update

    function subscribe(listener) {
        if (typeoflistener ! = ='function') { throw new Error()}// The listener passed in must be a function
        if (isDispatching) { throw new Error()}// No dispatch operation is currently in progress
        var isSubscribed = true;
      	// keep a snapshot of currentListeners. The currentListeners and currentListeners are independent of each other and point to different references.
        ensureCanMutateNextListeners();
        nextListeners.push(listener); // Note that the nextListeners are now newer than the currentListeners, which are pre-push listeners
        return function unsubscribe() { // Returns a function to cancel the current subscription
          if(! isSubscribed) {return; }if (isDispatching) { throw new Error() }
          isSubscribed = false;
          ensureCanMutateNextListeners();// Save a snapshot of the current listeners
          var index = nextListeners.indexOf(listener);
          nextListeners.splice(index, 1); // Delete the function currently monitored on the listeners
          currentListeners = null;
        };
      }
    Copy the code
  4. Unsubscribe () Unsubscribe event

    The return value of the subscription function is a function to cancel the current subscription, which can be unsubscribed by calling it directly

Note how many listeners are currently available on the listeners and how many are currently available on the listeners? In subscribe and unsubscribe events are called ensureCanMutateNextListeners function do? ?

// If there is a subscription event inside the subscription event
store.subscribe(function() {
  console.log(1)
  // The following code is executed only when the current callback function executes
  store.subscribe(function() { 
    console.log(2)})})/ / if we simulate dispatch, don't call in the event of subscription ` ensureCanMutateNextListeners ` function, then print the result of what is it?
for (var i = 0; i < listeners.length; i++) {
  var listener = listeners[i];
  listener();
}
// both 1 and 2 are printed
/ / call ` ensureCanMutateNextListeners ` function, will only print 1

Copy the code

The main purpose of Redux is to make subscription and unsubscribe events that occur during dispatch execution not immediately present but at the next dispatch, so Redux does two things:

1. Create a copy of currentListeners before each subscription or unsubscribe event, pointing both currentListeners and nextListeners to different references respectively. Subsequent listeners are valid only for nextListeners. CurrentListeners remain the same

2. During each dispatch, currentListeners and nextListeners point to the same reference after the Reducer function is executed.

Everything seemed to be going well, but when I first started the experiment, I used the map method of the array instead of the for loop to iterate over the listener function, which unexpectedly only printed 1, meaning that the current change was not immediately reflected.

listeners.map( listener= > listener() ) // Only 1 is printed
Copy the code

If the array changes during map traversal, will the result of the traversal be affected?

var arr1 = [1.2.3.4.5]
var arr2 = []

var arr3 = arr1.map(item= > {
  if(item ===1) {
    arr1.push(100)}return item
})
arr3 // [1, 2, 3, 4, 5
var arr4 = arr1.map(item= > {
  if(item ===1) {
    arr1.push(100)}return item
})
arr4 // [1, 2, 3, 4, 5, 100
Copy the code

Not only map, but also forEach, but I can’t find the reason on the Internet, I wonder if these methods also do the same processing as Redux when encapsulation (the current change is not immediately reflected, the next trigger will be reflected);

combineReducers

The state of the entire application is an object tree, which contains all the states in the application. Each component usually takes part of the data from this big object tree. Moreover, as the application becomes more and more complex, the huge Reducer functions need to be split so that each reducer is only responsible for a part of the data in the Object tree. In this case, we need a main function to combine these sub-states generated by the reducer into a complete object tree with the same structure as the previous one.

So all combineReducers needs to do is return a function that is the reducer function, the first parameter we passed to createStore, and that needs to combine and return the child states generated by the reducer. Say a little round ah ~, directly into the source code

function combineReducers(reducers) {// Note that the reducers here are a series of reducer objects as key values
  var reducerKeys = Object.keys(reducers); // Get the key name of the object
  var finalReducers = {}; // Shallow copy Final reducer that meets all conditions

  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i];
    if (typeof reducers[key] === 'function') { //reducer must be a function,
      finalReducers[key] = reducers[key];  // Function shallow copy}}var finalReducerKeys = Object.keys(finalReducers); // Obtain the final reducer key name

  return function combination(state, action) { // Accept a state and an action?? This is the Reducer function
    Void 0 = undefined; void 0 = undefined; void 0 = undefined; Void 0 is less than undefined9;
    if (state === void 0) { 
      state = {};
    }

    var hasChanged = false; // Check whether the state has changed
    var nextState = {};

    //
    for (var _i = 0; _i < finalReducerKeys.length; _i++) { 
      var _key = finalReducerKeys[_i]; / / for kek
      var reducer = finalReducers[_key]; Obtain the reducer according to the key
      var previousStateForKey = state[_key]; // Obtain the corresponding child state according to the key
      var nextStateForKey = reducer(previousStateForKey, action);Call the reducer function to return the new state
      nextState[_key] = nextStateForKey; // Combine State according to reducerKey
      // shallow comparison to determine whether the current child state hasChanged, hasChanged is true only once during the traversalhasChanged = hasChanged || nextStateForKey ! == previousStateForKey; }// If the state is null and the value returned by reducer is undefined, hasChanged cannot be determined. Because nextStateForKey and previousStateForKey are both undefined. Therefore, the following line of code is for those attributes that are not defined in the original state, but still return undefined after reducer calculationhasChanged = hasChanged || finalReducerKeys.length ! = =Object.keys(state).length;
    return hasChanged ? nextState : state;
  };
}
Copy the code