Source code series:

  • SPA Router v5.x SPA Router v5.x SPA Router
  • Redux v4. X source code analysis

The following is an interpretation of the Redux V4.x API source code.

createStore

createStore(reducer, [preloadedState], enhancer)

The createStore method accepts three parameters, the first is reducer, the second is preloadedState (optional), and the third is enhancer. Returns an object store. Store contains methods dispatch, getState, subscribe, replaceReducer, and [? Observable].

The reducer parameter may be a reducer. If there are multiple reducer, the return function is a reducer after the combineReducer function is executed.

PreloadedState specifies the initial state of the createStore function. If this parameter is not passed and enhancer is a function, the createStore function will use enhancer as the second parameter.

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
Copy the code

The enhancer parameter is a store enhancer and is a function. The createStore parameter is used as the enhancer function. The reducer and preloadState parameters are also passed in the return function. The execution ultimately returns an enhanced store. Enhancer: applyMiddleware() returns value, react-devrools() returns value, enhancer: applyMiddleware() returns value, react-devrools() returns value

  if (typeofenhancer ! = ='undefined') {
    if (typeofenhancer ! = ='function') {
      throw new Error('Expected the enhancer to be a function.')}return enhancer(createStore)(reducer, preloadedState)
  }

Copy the code

Before introducing methods, here are some variables:

  let currentReducer = reducer   // Current reducer
  let currentState = preloadedState    // Current state
  let currentListeners = []   // The current listener
  let nextListeners = currentListeners  // Backup of listener
  let isDispatching = false  // Whether the dispatch action is being performed
Copy the code

dispatch

dispatch(action)

The Dispatch method takes an action, which is an object that must include the type attribute or an error will be reported. Inside the dispatch function, the reducer() function was executed to obtain the latest state, assign the value to currentState, execute the subscriber, and the Dispatch function returns an action object. The source code is as follows:

  function dispatch(action) {
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant? ')}if (isDispatching) {  // If dispatch is triggered again, an error is reported
      throw new Error('Reducers may not dispatch actions.')}try {
      isDispatching = true  // Indicates that 'dispatch' is being executed
      currentState = currentReducer(currentState, action)    // Execute the latest Reducer to return the latest state
    } finally {
      isDispatching = false
    }

    const listeners = (currentListeners = nextListeners)  // Each dispatch execution is performed by the subscriber
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

Copy the code

getState

getState()

To get the latest state, return currentState. The source code is as follows:

  function getState() {
    return currentState
  }
Copy the code

subscribe

Add an event subscriber that executes each time a Dispatch (action) is triggered. Returns the unSubscribe method unSubscribe. The observer/publisher-subscriber pattern is used here. The source code is as follows:

Note: The subscriber must be a function, otherwise an error is reported.

function subscribe(listener) {
    if (typeoflistener ! = ='function') {  // Subscribers must be functions
      throw new Error('Expected the listener to be a function.')}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#subscribe(listener) for more details.')}let isSubscribed = true

    nextListeners.push(listener)     // Subscribe to a message, which is received by the subscriber each time it dispatches (action)

    return function unsubscribe() {  // Returns the unsubscribe method
      if(! isSubscribed) {return
      }

      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#subscribe(listener) for more details.'
        )
      }

      isSubscribed = false

      const index = nextListeners.indexOf(listener) 
      nextListeners.splice(index, 1)  // Cancel the subscriber}}Copy the code

observable

Make an object observable. The observed object must be an object; otherwise, an error is reported. Here, the author uses the third-party library symbol-Observable to observe currentState. The source code is as follows:

ECMAScript Observables is currently a draft and is not yet in use.

function observable() {
    const outerSubscribe = subscribe
    return {
      subscribe(observer) {
        if (typeofobserver ! = ='object' || observer === null) {
          throw new TypeError('Expected the observer to be an object.')}function observeState() {
          if (observer.next) {
            observer.next(getState())   // Listen for currentState
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [?observable]() {
        return this}}}Copy the code

replaceReducer

Replace reducer. This is not needed. The source code is also simple, assigning nextReducer to currentReducer. The source code is as follows:

  function replaceReducer(nextReducer) {
    if (typeofnextReducer ! = ='function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
  }
Copy the code

The above methods dispatch, getState, subscribe, replaceReducer, [? Observable] use closures. Variables such as currentState, currentReducer, currentListeners, nextListeners, and Isregularly are referenced. For example, if Dispatch changes currentState, the corresponding other methods will also change currentState, so there is a connection between these methods.

applyMiddleware

There are many ways to implement middleware, including custom ones, such as printing logs, and well-known ones, such as Redux-Chunk, Redux-Promise, and Redux-Saga, which will be analyzed later.

ApplyMiddleware () is usually the third parameter to the createStore method mentioned above.

enhancer(createStore)(reducer, preloadedState)
Copy the code

Combined with the above code, this is equivalent to

applyMiddleware(... middlewares)(createStore)(reducer, preloadedState)Copy the code

ApplyMiddleware uses functional programming, receiving an array of middlewares as its first argument, a function of createStore as its return value, and a dispatch function as its return value. Receive an action such as (Reducer, state) and return the enhanced store.

The source code is as follows:

function applyMiddleware(. middlewares) {
  return createStore= >(... args) => {conststore = createStore(... args)//  args 为 (reducer, initialState)
    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 = {   // 给 middleware 的 store
      getState: store.getState,
      dispatch: (. args) = >dispatch(... args) }const chain = middlewares.map(middleware= > middleware(middlewareAPI))  // chain is an array of anonymous functions with arguments next (functions returned by middleware execution)dispatch = compose(... chain)(store.dispatch)// The result returned by the compose function is middlewares wrapped dispatch

    return {   // Return to the dispatch enhanced store. store, dispatch } } }Copy the code

compose

The source code is as follows:

 function compose(. funcs) {
  if (funcs.length === 0) {
    return arg= > arg
  }

  if (funcs.length === 1) {
    return funcs[0]}return funcs.reduce((a, b) = >(... args) => a(b(... args))) }Copy the code

Although the source code is only a few lines, the author’s implementation is too powerful, if it is a bit difficult to understand, can look at the following example analysis:

var arr = [
  function fn1(next){
    return action= > {
      // Next is fn2 return (dispatch function)
      const returnValue = next(action)
      return returnValue
    }
  },
  function fn2(next) {
    return action= > {
      // Next is fn3 return function (dispatch function)
      const returnValue = next(action)
      return returnValue
    }
  },
  function fn3(next3) {
    return action= > {
      // Next is dispatch.
      const returnValue = next3(action)
      return returnValue
    }
  },
]

var fn = arr.reduce((a, b) = > (args) => a(b(args)))
fn / / (args) = > a (b) (args) is equivalent to (dispatch) = > fn1 (fn2 (fn3 (dispatch)))

// Order of reduce function execution:
// First time:
ƒ FN1 (ARgs)
ƒ Fn2 (args)
Return value: (args) => fn1(fn2(args))

// The first return value is assigned to a
// variable A: (args) => fn1(fn2(args))
ƒ FN3 (ARgs)

// For the second time:
// variable A: (args) => fn1(fn2(args))
ƒ FN3 (ARgs)
Return value: (args) => fn1(fn2(fn3(args))
Copy the code

Compose (f, g, h) returns (… args) => f(g(h(… Args))), where F, G and H are each middleware.

Dispatch = compose(… (store. Dispatch) => f(g(h(store. Dispatch))) Return a dispatch function (an action function) as an argument to the next piece of middleware, passing through layers and finally returning a wrapped Dispatch function.

Here is an example of print logging middleware:

const logger = store= > next => action= > {
  console.log('dispatch', action)
  next(action)
  console.log('finish', action)
}

const logger2 = store= > next2 => action2= > {
  console.log('dispatch2', action2)
  next2(action2)
  console.log('finish2', action2)
}
...
const store = createStore(rootReducer, applyMiddleware(logger, logger2))
Copy the code

Note: However, if you use store.dipsatch() instead of next() in one of your middleware, it will go back to the starting position, causing an infinite loop.

Each time a dispatch is triggered, the middleware executes, printing in the order dispatch distapch2 Finish2 Finish.

combineReducer

If there are multiple reducers, combine them into a reducer and pass it to the function createStore(Reducer).

The core source code is as follows:

function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]  // reducer must be a function}}const finalReducerKeys = Object.keys(finalReducers)

  return function combination(state = {}, action) {  // Returns a new reducer function
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) { // Dipatch (action) is executed once each time, and all reducer tasks are executed
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      constnextStateForKey = reducer(previousStateForKey, action) nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey ! == previousStateForKey }return hasChanged ? nextState : state  // Determine whether the state has changed, and return the new state if so}}Copy the code

bindActionCreators

This API can be interpreted as: methods to generate actions. It mainly encapsulates dispatch into bindActionCreator(actionCreator, Dispatch) method, so dispatch(Action) can be triggered directly when calling, without manually calling Dispatch. FetchPeople ({type: type, text: ‘fetch people’})); ‘fetch people’})

The source code is as follows:

function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this.arguments))}}function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

Copy the code

reference

  • redux github

communication

Here is the blog address, feel good to click a Star, thank you ~

Github.com/hankzhuo/Bl…