Source code interpretation of Redux — source code analysis of createstore

Time:2021-5-11

Source code analysis of createstore

createStoreyesreduxThe core module. This module is used to create astoreAt the same time, exposed to the outside worlddispatch,getState,subscribeandreplaceReducerMethods( Source code aboutobservableI can ignore the part of. This isreduxFor internal use. We hardly use it in development)

Let’s take a look at the basic structure of this module

Source code interpretation of Redux -- source code analysis of createstore

rely on

  • lodash/isPlainObject
  • symbol-observable

Export

  • dispatch
  • getState
  • subscribe
  • replaceReducer
  • [$$observable](hardly used)

Redux source code used in thelodash/isPlainObjectDependence. stayIE6-8The neutral energy is very poor, and its implementation mode is different from thatjQuery3.xThe implementation of is similar to that in the old version ofIEI can’t support it. Finally, I will discuss it with you.

Source code comments

//Determine whether it is a pure object module ({})
import isPlainObject from 'lodash/isPlainObject'
//Introducing observable support
import $$observable from 'symbol-observable'
export const ActionTypes = {
  INIT: '@@redux/INIT'
}

This one up there isreduxOne for internal useaction. Mainly used for internal testing and rendering the initial state. Remember, we write it ourselvesactionWhen I was young,action.typeNot for@@redux/INIT. Because the action will be in thereduxInternal automatic call of. For example, the following trick Code:

import {createStore, combineReducers, applyMiddleware} from '../src'
import logger from 'redux-logger'

const actionTypes = '@@redux/INIT'
const reducers = (state = {}, action) => {
  switch(action.type) {
    case actionTypes:
      console.log('hello @@redux/INIT')
      return {
        'type': actionTypes
      }
    default:
      return state
  }
}
const store = createStore(reducers, applyMiddleware(logger))
console.log('*************************************')
Console.log (store. Getstate()) // will be rendered as {'type': '@ @ Redux / init'}
console.log('*************************************')

Here we arecreateStoreThe implementation of the method is as follows

export default function createStore(reducer, preloadedState, enhancer){
  //Judgment and setting of initial conditions
  function getState() {
    //Implementation of getstate method
  }
  function subscribe() {
    //Implementation of subscribe method
  }
  function dispatch() {
    //Implementation of dispatch method
  }
  function replaceReducer() {
    //Implementation of replacereducer method
  }
  function observable() {
    //Implementation of observable method
  }
  //After the store is created, an 'init' action is automatically distributed. Render the initialized state tree.
  dispatch({ type: ActionTypes.INIT })
}

Let’s analyze what each line of code does

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

When callingcreateStoreMethod, you can pass three parameterscreateStore(reducer, preloadedState, enhancer). The attributes of each parameter are as follows:

  • reducerRequired parameters,functiontype
  • preloadedStateOptional parameters,objecttype
  • enhancerOptional parameters,functiontype

In normal use, we usually omit the second parameter. For example, when we need to useWhen using Redux MiddlewareIt passes one as the third parameterapplyMiddleware()[the return value is afunction]。 If we have no initial state, we omit the second parameter. At this time, our function call form is:

const store = createStore(reducer, applyMiddleware(...))

At this time, the code in the above source code will be executed to make the function call satisfy the most basic form call. That is, when a function passes two or three parameters, its internal processing logic is the same.

//If we specify reducer enhancer enhancer
if (typeof enhancer !== 'undefined') {
  //Enhancer must be a function
  if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    //This function takes createstore as a parameter and returns a function. The function takes reducer, preloaded state as a parameter
    //Directly return the object wrapped by enhancer
    return enhancer(createStore)(reducer, preloadedState)
  }

To better understand this code, you can refer to the internal implementation of applymiddleware.

//It is required that the first parameter passed to the createstore must be a function
if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
 }
//Save the original reducer
let currentReducer = reducer
//Save the initial state
let currentState = preloadedState
//Save all event listeners
let currentListeners = []
//Gets a copy of the current listener (same reference)
let nextListeners = currentListeners
//Is action being distributed
let isDispatching = false

function ensureCanMutateNextListeners() {
  //If nextlisteners and currentlisteners have the same reference, a copy of the current event listener collection is obtained and saved to nextlisteners
  if (nextListeners === currentListeners) {
    nextListeners = currentListeners.slice()
  }
}

It’s up therecreateStoreFor some initial parameters in the method, here is a place worth thinking about:Why maintain two lists of event listeners (nextlisteners, current listeners)?. Next, we will explain.

//Returns the state of the current store directly
function getState() {
  return currentState
}
function subscribe(listener) {
    //The event listener must be a function or an exception will be thrown
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }
    //Whether the event listener has been canceled or not (personal feeling: the initial value should be set to false, which is better for semantics.)
    let isSubscribed = true
    //The result of calling this function is to generate a copy of the current event listener and save it to nextlisteners
    ensureCanMutateNextListeners()
    //Add a new event listener to nextlisteners
    nextListeners.push(listener)
    
    //Returns a function that cancels listening
    return function unsubscribe() {
      //If the listener has been canceled, return directly
      if (!isSubscribed) {
        return
      }
      //Set the listener cancel flag to false
      isSubscribed = false
      //Again, a copy of the event listener collection is generated
      ensureCanMutateNextListeners()
      //Gets the index of the event listener to cancel
      const index = nextListeners.indexOf(listener)
      //Remove this event listener from the event listener collection
      nextListeners.splice(index, 1)
    }
  }

fromsubscribeMethod can be seen in the source code, each time in the listenerAdd / removePreviously, a copy is generated based on the current listener set and saved to thenextListenersIn the middle. At this time, we still can’t answer the above questions accurately. Let’s continue to studydispatchSource code:

function dispatch(action) {
    //The parameter of dispatch is the action we need to distribute. We must ensure that the action is a pure object
    //If it is not a pure object, an exception is thrown.
    if (!isPlainObject(action)) {
      //This method has a hole, in the lower version of IE browser neutral performance is very poor, finally we will study the source code of this method.
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    //The action you distribute must have a type attribute (we can think of this attribute as the ID card of the action, so that Redux can know which action you distribute, what you need to do, and how to do it for you)
    //Without this property, an exception is thrown
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }
    
    //If Redux is dispatching action, an exception will be thrown? When will this happen???
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      //Distribute action
      //The essence is to pass the current state and the action you need to distribute to the reducer function and return a new state
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    //This is also a very important part. Ha ha ha ha ha ha ha, there is another list of event listeners. Let's briefly talk about the functions of these three lists
      //Nextlisteners: save the list of all event listeners that need to be triggered after this dispatch
    //Current listeners: saves a copy of the nextlisteners list
      //Listeners: list to execute
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      //Call all event listeners
      listener()
    }
    //The return value of dispatch is also very important. Without this return value, it is impossible to introduce a powerful middleware mechanism.
    return action
  }

Here we can answer this question:Why maintain two lists of event listeners (nextlisteners, current listeners)?

The first thing we have to know is: when will our listeners execute? After we call dispatch to dispatch action. OK, look at the picture below

Source code interpretation of Redux -- source code analysis of createstore

This graph shows that whendispatchMethod to this line of code,listenerscurrentListenersnextListenersThese three variables refer to the same array in memory. As long as one of them changes, the other two change immediately. It has the same meaning as the following example:

Source code interpretation of Redux -- source code analysis of createstore

So, in this case.If I call a listener in an event listener function, after this dispatch, the event listener canceled will not be executed.?? Is that right.

import {createStore, combineReducers, applyMiddleware} from '../src'
import logger from 'redux-logger'

const actionTypes = '@@redux/INIT'
const reducers = (state = {}, action) => {
  switch(action.type) {
    case actionTypes:
      return {
        'type': actionTypes
      }
    default:
      return state
  }
}
const store = createStore(reducers, applyMiddleware(logger))

const listener1 = store.subscribe(() => {
    console.log('listener1')
})


const listener2 = store.subscribe(() => {
    //Cancel listener 3
    listener3()
    console.log('listener2')
})


const listener3 = store.subscribe(() => {
    console.log('listener3')
})

store.dispatch({type: actionTypes})

The results are as follows

listener1
listener2
listener3

The results are as follows:Even if you cancel other event listeners in an event listener, the cancelled event listener will still be executed after this dispatch. in other words.Redux will ensure that after a certain dispatch, all event listeners before the dispatch will be executed.

Is this a bug or a feature. I don’t know, but from the Redux source code, we can know that this is a bug. Therefore, the author of Redux skillfully avoided this situation by using the above method. In fact, the implementation method is very simpleCut off the same reference relationship between nextlisteners and currentlisteners

Source code interpretation of Redux -- source code analysis of createstore

Let’s go on

//The method of reducing is proposed( Dynamic load reducers)
function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    //After the replacement, reinitialize
    dispatch({ type: ActionTypes.INIT })
  }
//The main purpose of triggering the default action is to generate the initial state tree structure
dispatch({ type: ActionTypes.INIT })
//That's familiar
return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
      //Nima ignored this
    [$$observable]: observable
  }

That’s rightcreateStoreAn overall interpretation of the source code, the level is limited, welcome to brick. The following source code interpretation and test examples can focus on:Redux source code interpretation warehouse

Recommended Today

Looking for frustration 1.0

I believe you have a basic understanding of trust in yesterday’s article. Today we will give a complete introduction to trust. Why choose rust It’s a language that gives everyone the ability to build reliable and efficient software. You can’t write unsafe code here (unsafe block is not in the scope of discussion). Most of […]