preface

Redux is a react-redux library for React projects. Redux is a react-redux library for React projects. Redux doesn’t have to rely on React to work, though. It can also be used with other interface libraries.

Redux can be found in the Redux Chinese documentation:

Redux is a JavaScript state container that provides predictable state management.

In common terms: Redux created a space in memory to store state variables, and internally formulated a set of rules mechanism (Reducer) to update state. Consumers can update (dispatch) and get (getState) state through specific methods provided internally by Redux.

This article will read the source code to learn the properties and methods provided by Redux to gain a deep understanding of the overall flow of Redux.

Redux is easy to use

const redux = require('redux');

// state defines the collection
const iniState = {
  count: 0
}

Reducer - A pure function that provides cases to update state
function reducer(state = iniState, action) {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, count: state.count + 1 };
    case "DECREMENT":
      return { ...state, count: state.count - 1 };
    default:
      returnstate; }}// store creates the Redux repository
const store = redux.createStore(reducer);

Action Defines the update action, which corresponds to the update case in the Reducer function
const action1 = { type: "INCREMENT" };
const action2 = { type: "DECREMENT" };

// Listen on state and subscribe cb when state changes
store.subscribe(() = > {
  console.log('Redux data changed! ');
});

// Dispatch triggers the action update action, and then go to the Reducer to find the corresponding case to update the state
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action1);

// getState Gets the state in the store
console.log(store.getState());
Copy the code

In the above use case, we focused on the following points:

  • Define the set of states in the Redux Store:state;
  • Define a pure function to update the status:reducer;
  • Create a Redux store and accept reducer as a state update function:createStore;
  • Defines the type of action that changes the state, which can be an object or a function:action;
  • Methods to trigger the update action:dispatch;
  • Register status change listening events:subscribe;
  • Get the state in the Redux Store:getState.

The Redux update flow can be seen in the above use case:

  • throughdispatchSend update actionactionreducerIn pure functions;
  • Match the corresponding cases in the Reducer function, and then update the statestate.

Redux source code interpretation

createStore

Redux creates a state container through createStore, which receives a pure reducer function as a required parameter and middleware as a passable parameter to extend specific functions, and finally returns a Store instance object.

const redux = require('redux');

// The middleware component method is composed for composing.
const thunk = ({ dispatch, getState }) = > (next) = > (action) = > {
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }
  return next(action); // Next is store.dispatch
};

const store = redux.createStore(reducer, redux.applyMiddleware(thunk));
Copy the code

Its source code implementation is as follows:

function createStore(reducer, preloadedState, enhancer) {
  // 1. Parameter conversion: When the createStore second parameter is a function and the third parameter is not passed, the second parameter is processed as enhancer middleware logic
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState;
    preloadedState = undefined;
  }

  When using middleware, enhancer (applyMiddleware) is called and the store is returned
  if (typeofenhancer ! = ='undefined') {
    return enhancer(createStore)(reducer, preloadedState);
  }

  // if the middle is not used, a store object is created
  let currentReducer = reducer;
  let currentState = preloadedState;
  let listeners = [];
  let isDispatching = false;

  function getState() {
    // ...
  }

  function subscribe(listener) {
    // ...
  }

  function dispatch(action) {
    // ...
  }

  const store = {
    dispatch,
    subscribe,
    getState,
  };

  return store;
}
Copy the code

CreateStore is divided into two scenarios: scenarios that use middleware and scenarios that do not use middleware. Let’s start with the simplest scenario: scenarios that do not use middleware.

From the above source we know: Store is an object, provides dispatch, subscribe, getState three methods, then one by one to analyze the internal implementation of each method.

getState

What getState does is very simple and straightforward: it returns the collection of states in the store. GetState uses the JS closure to get the state variable created when the createStore is executed.

function createStore(reducer, preloadedState, enhancer) {
  // ...
  let currentState = preloadedState;
  
  function getState() {
    return currentState;
  }
  // ...
}
Copy the code

subscribe

Subscribe is used to register and destroy listen callbacks, which are triggered to execute when the state is updated. It takes a function as an argument and returns a new function that destroys registered listeners.

function createStore(reducer, preloadedState, enhancer) {
  // ...
  let listeners = [];
  
  function subscribe(listener) {
    / /... Omit throw Error if the listener is not a function

    listeners.push(listener);
    return function unsubscribe() {
      const index = listeners.indexOf(listener);
      listeners.splice(index, 1); }}// ...
}
Copy the code

dispatch

Dispatch is used to dispatch an action type and enter the Reducer pure function under the store to match the case and update the state. After a status update, the listener function in Listners is triggered.

function createStore(reducer, preloadedState, enhancer) {
  // ...
  let currentReducer = reducer;
  let currentState = preloadedState;
  let listeners = [];
  let isDispatching = false;
  
  function dispatch(action) {
    / /... Omit throw Error if action is not an object
    / /... Throw Error if action. Type does not exist

    // Execute the pure reducer function to update state
    try {
      isDispatching = true;
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    // Notify the listener when state is updated
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i];
      listener();
    }

    return action;
  }
  // ...
}
Copy the code

applyMiddleware

Let’s look at the scenarios using middleware from a source code perspective.

const thunk = ({ dispatch, getState }) = > (next) = > (action) = > {
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }
  return next(action); // Next is store.dispatch
};

const store = redux.createStore(reducer, redux.applyMiddleware(thunk));
Copy the code

When we take the result of applyMiddleware execution as the second parameter to createStore, we enter the middleware’s processing logic:

function createStore(reducer, preloadedState, enhancer) {
  // Parameter conversion
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState;
    preloadedState = undefined;
  }

  // Scenarios using middleware
  if (typeofenhancer ! = ='undefined') {
    return enhancer(createStore)(reducer, preloadedState);
  }
  
  // ...
}
Copy the code

ApplyMiddleware is called as a Currization function, and createStroe and Reducer are the currization function parameters. Its source code is as follows:

function applyMiddleware(. middlewares) {
  return (createStore) = > (reducer, preloadedState) = > {
    Call createStore to create a store object
    const store = createStore(reducer, preloadedState);

    // 2. Define a new dispatch. The essence of middleware is to transform the dispatch and return a more powerful dispatch
    let dispatch = () = > {};

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ... args) = >dispatch(action, ... args) }// Each middleware also follows the currification principle of functions, taking getState and Dispatch as arguments first
    const chain = middlewares.map(middleware= > middleware(middlewareAPI));
    // Middleware is executed through compose to return the middleware enhanced Dispatchdispatch = compose(... chain)(store.dispatch);return {
      ...store,
      dispatch
    }
  }
}
Copy the code

The same thing you do in applyMiddleware is execute reducer as an argument to createStore and get a store, the only difference being that you call compose to organize the middleware and return a modified, enhanced dispatch method.

Compose source code implementation is as follows:

function compose(. funcs) {
  if (funcs.length === 0) {
    // If there is no middleware, a function is returned, in which case the arG is store.dispatch, continuing with the original dispatch method
    return (arg) = > arg;
  }

  if (funcs.length === 1) {
    // If there is only one middleware, that middleware is returned for transformation dispatch
    return funcs[0];
  }

  // Multiple middleware cases:
  Compose (fn1, fn2, fn3)(... Args converts to fn1(fn2(fn3(... args)))
  // It is a high-order aggregate function that executes fn3, passes the result to Fn2, and passes the result to FN1.
  return funcs.reduce((a, b) = > (. args) = >a(b(... args))); }Copy the code

In the compose method, the difficulty and core is the order in which the middleware is executed when dealing with multiple middleware components: The later middleware executes first, and the result returned (which is a new dispatch method modified/enhanced by this middleware) is modified/enhanced as an argument to the next middleware.

After the transformation of middleware, the dispatch we use has been transformed/enhanced by middleware, and other functions remain unchanged. Therefore, we have clarified a concept here: The middleware of Redux is used to transform/enhance the Dispatch method.

Let’s further analyze the transformation process of Dispatch based on the middleware redux-Thunk.

redux-thunk

Redux-thunk Is the core implementation of applyMiddleware.

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

First, thunk middleware executes once in applyMiddleware, taking Dispatch and getState as arguments:

// ...
let dispatch = () = > {};

const middlewareAPI = {
  getState: store.getState,
  dispatch: (action, ... args) = >dispatch(action, ... args) }// Each middleware also follows the currification principle of functions, taking getState and Dispatch as arguments first
const chain = middlewares.map(middleware= > middleware(middlewareAPI));
// ...
Copy the code

Note: The dispatch passed at this point is just an empty function and has not been altered. Then call compose to execute the middleware to transform the Dispatch. Since we only have one Thunk middleware here, we use that middleware directly in Compose to transform:

// ...dispatch = compose(... chain)(store.dispatch);// ...
Copy the code

The above code calls Thunks middleware and passes store.dispatch (original dispatch) as a parameter. At this time, the result returned is the new and modified Dispatch, and the corresponding code is as follows:

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

From the above code analysis:

  • nextAnd that corresponds tostore.dispatch, the result returned by the currified function:(action) => { ... }It’s the new onedispatchFunctions;
  • inThe new dispatchFunction, allowsactionThe value is a function calledactionFunction and will transform thedispatchRepass;
  • inactionA guarantee is required when a function calls dispatch internallyThe new actionIs an object, and if it is still a function, the above work is repeated;
  • Because only there istypeProperty of the objectreturn next(action)To call the originalstore.dispatchTo enter thereducerUpdate state in pure functions.

combineReducers

When our project is large, a reducer pure function cannot meet our needs, so we hope to divide modules, each module corresponds to its own state and reducer, so combineReducers appear, let’s first look at its usage:

import { combineReducers } from 'redux';

function reducer1(state = {}, action) {
  // ...
}

function reducer2(state = {}, action) {
  // ...
}

const reducer = combineReducers({
  reducer1,
  reducer2
});

let store = createStore(reducer);
Copy the code

CombineReducers receives a Reducer object set and returns a new Reducer to manage each reducer sub-module. Its source code is as follows:

function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers);

  // Returns a new Reducer pure function
  return function combination(state, action) {
    let hasChanged = false; // Record whether the state has changed in this update
    const nextState = {};

    for (let i = 0; i < reducerKeys.length; i++) {
      const key = reducerKeys[i];
      const reducer = reducers[key];
      const previousStateForKey = state[key];
      const nextStateForKey = reducer(previousStateForKey, action); / / reducer for executionnextState[key] = nextStateForKey; hasChanged = hasChanged || nextStateForKey ! == previousStateForKey; } hasChanged = hasChanged || reducerKeys.length ! = =Object.keys(state).length;
    
    returnhasChanged ? nextState : state; }}Copy the code

CombineReducers returns a new Reducer function. It traverses each reducer submodule and updates the state corresponding to the submodule and this update action as parameters of the reducer pure function of the submodule.

So when using combineReducers, there is a point to note:

  • In more than onereducerThe following, declaredcaseNot the same name. When according toaction.typeTo find thecaseWill all have the sameaction.typeThe Reducer function executes and updates the state, which is prone to unexpected bugs.

At the end of the article

If there are inadequacies in the compilation of this article, 👏 welcomes readers to put forward valuable suggestions, the author to correct.