I don’t think Redux is very friendly for beginners, many concepts are not very easy to understand, and it is necessary to read the source code after using Redux

createStore

Redux’s Store stores the application’s state tree, which can be changed only through the Dispatch () method.

Although the documentation clearly states that there can only be one store, I work on projects where there may be more than one store, such as multi-page applications.

parameter

CreateStore has three parameters, which are Reducer, preloadedState and enhancer.

  1. reducer. This is the incomingreducerUsually throughcombineReducersIntegration of reducer
  2. preloadedState. When initializing the Store, it is necessary to pass in an initial state, both to give the app page an initial value and to let yourself or others know the state structure of the entire app.
  3. enhancer. It’s usually just some middleware for Redux, and the concept of middleware is a little convoluted.

CreateStore does a parameter check and type check, and the reducer is not required except that it was passed. The forms passed in include the following.

  • createStore(reducer)
  • createStore(reducer, enhancer)
  • createStore(reducer, preloadedState)
  • createStore(reducer, preloadedState, enhaner)

Internal implementation

CreateStore internally contains three important variables and four methods that can be called externally.

variable

  1. currentState. Stores the state tree of the entire store management.
  2. currentListeners. Story is actuallyObserver modelA practice of. We can think of currentListeners as a list of functions to execute after state changes.
  3. isDispatching. Is it ongoing?dispatch.
  4. nextListners. When doingsubscribeTo do this, push the new listener function tonextListnersArray as a snapshot of the latest Listener array.

methods

  1. GetState Specifies the only way to access state inside a store. Method directlycurrentStateInternal variables return directly.
function getState() {
  return currentState;
}
Copy the code
  1. Subscribe is used to add a listener tonextListners. For everydispatchMethod,nextListenrsreplacecurrentListeners, the incoming listener will be executed. Normally we pass in a UI render as a subscribe listener, and every time the state changes, redux tells the UI to render. The listener does not see every change in all states, because states may change several times in dispatch before the listener is executed.subscribeMethod returns a function closure as an unsubscribe.
function subscribe() {
  let isSubscribed = true
  ensureCanMutateNextListeners()
  nextListeners.push(listener)

  return function unsubscribe() {
    if(! isSubscribed) {return
    }
    isSubscribed = false
    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)}}Copy the code
  1. The only way to change state outside of Dispatch. Call Reducer to get the latest state(reducer is the function that builds the state tree) and execute each listener method. Dispatch can only be deliveredplain objectAction (plain JavaScript object) if you want to pass onethunk, requires middleware, redux-thunk.
function dispatch(action) {
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

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

    return action
  }
Copy the code
  1. ReplaceReducer Replace the reducer method. This method is rarely used.

combineReducers

Combine multiple reducers into one function. CombineReducers will call each reducer and merge the state returned by each reducer into the state tree.

parameter

The type is an object. Each key must correspond to a Reducer function.

const params = {
  key1: reducerfunc1,
  key2: reducerfunc2,
};
Copy the code

Assume that the state form returned by reducerFunc1 and ReducerFunc2 are respectively

const state1 = {
  v1: ' ',
  v2: 0,
};

const state2 = [];
Copy the code

So our state tree looks like this, and we get the state object from getState as follows.

const state = {
  key1: {
    v1: ' '.v2: 0,},key2: [],}Copy the code

Internal implementation

The code before the closure is reducer checks

  • Filter reducers that are not function reducers (each key must correspond to a Reducer method)
  • Check whether each Reducer method has initState and whether the default type returns state

The code is easy to understand.

export default function combineReducers(reducers) {
  return function combination(state = {}, action) {
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      Reducer state before the call
      const previousStateForKey = state[key]
      // State after the call
      const nextStateForKey = reducer(previousStateForKey, action)
      // Update the state tree snapshot
      nextState[key] = nextStateForKey
      // State Indicates whether there is a changed flag. If the Reducer execution results in a state change, a new state object is returned, so we can directly compare the object to determine whether there is a change.hasChanged = hasChanged || nextStateForKey ! == previousStateForKey }return hasChanged ? nextState : state
  }
}
Copy the code

applyMiddleware

Middleware, I think, is one of the more interesting and slightly difficult parts of redux as a whole.

You can view the documentation for the middleware on the official website. Middleware can be thought of as doing something before and after a Dispatch method. It can also be likened to Java facets.

export default function applyMiddleware(. middlewares) {
  return createStore= >(... args) => {conststore = createStore(... args)let dispatch = (a)= > {
      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: (. args) = >dispatch(... args) }const chain = middlewares.map(middleware= >middleware(middlewareAPI)) dispatch = compose(... chain)(store.dispatch)return {
      ...store,
      dispatch
    }
  }
}
Copy the code

In fact, this can be understood as Russian nesting dolls. The original store.dispatch is the innermost one of the nesting dolls. All the middleware are nested one by one according to the order of array. The nesting process is done by Compose.

To understand this, let’s also take a look at the source code for the middleware Redux-Thunk. Redux-thunk makes our action function. Attention!!!!! The original action can only be a plain Object. The following is the way redux-Thunk middleware is written: store, Dispatch, action must be passed before the actual function entity is executed. We’re using a Corrification, and we don’t execute the function body until we have enough arguments.

// Slightly modified redux-thunk
const thunk = ({ dispatch, getState }) = > next => action= > {
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }

  return next(action);
};

export default thunk;
Copy the code

Why was dispatch assigned to an empty function in the first place? Compose returns a new dispatch that replaces the empty function

const chain = middlewares.map(middleware= >middleware(middlewareAPI)) dispatch = compose(... chain)(store.dispatch)Copy the code

Finally, the store variable is returned.

return {
  ...store,
  dispatch
}
Copy the code

reference

  1. redux middleware
  2. Redux source