This article is only used to record personal reading of redux source code

Introduction: “Redux is a state management library with very minimal code. Middleware and functional programming are great.”

Redux characteristics

  1. State is unique. In an application, there should be only one top-level state
  2. Reducer is a pure function. A pure function means that the same output will only get the same output, an example of an impure function:function random() { return Math.random() }
  3. The state is read-only, and the state can only be modified by dispatching an action, and then reducers can match and directly modify the state, making it difficult to track back the time travel data

The directory structure


The directory is very simple and clear, the core code in

  1. createStore.js
  2. applyMiddleware.js
  3. combindReducers.js
  4. compose.js

A few other quick notes: Bindactionactions.js, bundled with a set of actions, simplifies the following actions (I haven’t used them very much…). The utils directory contains utility classes such as data passed at dispatch, whether it is a pure object, throwing a warning, etc

createStore

To construct the store

  1. Reducer is a pure function that will pass in the current state and an action to get a new state, preloadState, Is something like __INITIAL_STATE__, meaning state by default, enhancer
  2. Controls the entry because preloadState can be empty and enhancer can be empty, so it will existcreateStore(reducer,applyMiddleware(a, b)), this situation (personal doubt 🤔, why not make an objectCreateStore ({reducer, preloadState, enhancer})Enhancer, if there is an enhancerenhancer(createStore)(reducer, preloadedState)Enhancer (); enhancer ()enhancer(createStore,reducer preloadedState)What’s the difference?
  3. Define some variables
Let currentReducer = reducer // currentReducer let currentState = preloadedState // currentState let currentListeners = [] // Note How often are we talking about the current event listeners -- let nextListeners = currentListeners // How often are we talking about the current event listenersCopy the code
  1. Define a method: ensureCanMutateNextListeners nextListeners) (make sure can mutations, shallow copy nextListeners currentListeners device
  2. Define the getState method, first judge that if isDispatching is true, prevent you from obtaining state directly from the reducer (why)
  3. Define subscribe to accept a parameterlistenerFirst, determinelistenerIs it a function sumisDispatchingWhether the value is true is set to prevent the passed function from not being a function and to prevent the subscription from the reducer (because Diapatch triggers the reducer and dispatch triggers the reducer, and finally the reducer will burst)let isSubscribed = true, indicating that there is an event subscription, calledensureCanMutateNextListeners()And,nextListeners.push(listener), and finally returns an unsubscribe function, which determines ifisSubscribed === falseDoes not performTo prevent multiple triggering. In the case of isDispatching, the subscription is not allowed to be cancelledensureCanMutateNextListeners()To remove listeners from the nextListeners
  4. Define the dispatch function, and judge that the action must be a pure object with type field, and the actions are forbidden to be distributed in the Reducer, isDispatching is set to true,currentState = currentReducer(currentState, action)Call currentReducer, pass in the current state and action, get the new state, and finallycurrentListeners = nextListenersThat’s why nextListeners are needed, because redux uses a for loop to iterate over all events. If a listener is killed, the index changes, and a listener is lost, so a snapshot of the listener is saved.
  5. ReplaceReducer was defined and reducer was replaced. Reducer was not used and the usage scenario was not known
  6. Observable, which has no chance to use it, does not look carefully for the time being
  7. The last calldispatch({ type: ActionTypes.INIT }), initialize state, return
   return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [?observable]: observable
  }
Copy the code

applyMiddleware

The complete code for “personally considered the best part” is below

function applyMiddleware(middlewares) { return (createStore) => (... Args) => {const store = createStore(... args); Let dispatch = () => {throw new Error('Dispatching while constructing your middleware is not Allowed.' + 'Other middleware would not be applied to this dispatch.') const middlewareAPI = {getState: store.getState, dispatch: (Action) => dispatch(action)} // Middlewares.map (Middleware => middleware(middlewareAPI)); // Call compose(compose below) to compose the middleware sequence. dispatch = compose(... chain)(store.dispatch); // Finally return the enhanced dispatch {... store, dispatch }; }; }}Copy the code

So it is easy to see, middleware parameters and function body, must meet a certain format, otherwise there will be a problem, look at the source “redux-thunk”, to go through this process

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

};
}

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

Copy the code

export default thunk;

You can see, “redux-thunk” source code on this few lines, take this to go with the above source code again, start.

  1. Introduce this thunk
applyMiddleware([
  reduxThunk
])
Copy the code
  1. This thunk actually looks like this
({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
return next(action);
Copy the code

Copy the code

};

  1. Middleware executes a map, which executes again, and now looks like this
(next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
return next(action);
Copy the code

Copy the code

};

  1. Finally, compose is used to compose the middleware, because there is only one
(action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
return next(action);
Copy the code

Copy the code

};

Now look at the redux-Thunk official example to see if you understand

Function incrementAsync() {return (dispatch) => {setTimeout(() => {// Yay! Can invoke sync or async actions with `dispatch` dispatch(increment()); }, 1000); }; } // Call dispatch store.dispatch(incrementAsync()) in an anonymous functionCopy the code

The final decomposition of the enhanced dispatch is thunk1(thunk2(Thunk3 (store.dispatch))). The next of 1 is 2, the next of 2 is 3, and the next of 2 is 3, and the final next is obtained, which is store.d Ispatch (original Dispatch)

compose

In short, it is the execution of composition functions

function compose (... func) { if (func.length === 0) { return args => {}; }

If (func. Length === 1) {return func[0]} return func. Reduce ((a, b) => (... args) => a(b(... args)))

Copy the code

}

Compose => thunk1(thunk2(thunk3(store.dispatch)))

CombindReducers combine multiple reducer

Reducer is a reducer because it is the same as array.prototype. reduce(initValue, current), which uses the current value to make a change with the value to be changed to obtain a new value. It is an incremental process

  1. Receive reducers objects as follows:
{
  reducer1: Function,
  reducer2: Function
}

const defaultState = { count: 0, run: 'get Run' };

const reducer1 = function(state = defaultState, action) { if (action.type === 'add') { return state.count++; }

if (action.type === 'getValue') { return state.count; }

Copy the code

if (action.type === 'run') { return state.run; }};

  1. Filter out invalid reducer, that is, judge if the reducerX values are not functions, and directly eliminate them. Here finalReducers is used to remove the filtering, and the effective values will be stored in the reducer
  2. Returns a method called combination that takes state and Action parameters
  3. Define the hasChanged variable (to flag whether state hasChanged),nextState, the new state
  4. For loop traverses the finalReducer obtained at the second step of the mountain and obtains the following variables
Const reducerKeys = finalReducerKeys[I] const reducer = finalReducers[key previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action)Copy the code
  1. Check whether the nextStateForKey exists. If it does not, an exception will be thrownnextState[key]Put in this value
  2. The last judgmentnextState[key]! ==state[key]If not equal tohasChangedWill be set to true
  3. When the loop is complete, judge again
hasChanged = hasChanged || finalReducerKeys.length ! == Object.keys(state).lengthCopy the code
  1. Finally, depending on whether hasChanged is true, decide whether to return nextState or the existing state

From the above approach, I feel that there is a bad shortcoming, that is, each reducer change needs to go through all the existing reducer, which feels a waste of resources

That’s all the personal source code analysis for Redux

This article is formatted using MDNICE