background

Because I have to do an internship. So I’m going to start mending the holes. Like a plan to read the source code yourself. So today we’re going to talk about the redux source code. Redux-thunk and React-Redux will follow. With that out of the way, start reading the source code for a node library, such as EventProxy and Anywhere.

start

  • Overview, redux file structure

This may seem like a lot of files, but to understand redux’s internal implementation, look at createstore.js

, applymiddleware.js, combinereducers.js and compose. Let’s start with createstore.js.

  • createStore.js

    export default function createStore(reducer, preloadedState, enhancer) {
      // If the second argument is passed to enhancer instead of the initial state (the return value for the applyMiddleware call), assign the second argument, preloadedState, to enhancer
      if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
        enhancer = preloadedState
        preloadedState = undefined
      }
      // If an enhancer is passed but is not a function, an error is reported
      if (typeofenhancer ! = ='undefined') {
        if (typeofenhancer ! = ='function') {
          throw new Error('Expected the enhancer to be a function.')}// Otherwise, execute. Notice here. If the createStore is passed to an enhancer, the createStore will be passed to the enhancer, and the return value of the enhancer will also be a function. You can find out what's going on in applyMiddleware when we talk about it below.
        return enhancer(createStore)(reducer, preloadedState)
      }
    
      // If no enhancer is passed, proceed
      
      // Reducer is required to be a function. If not, an error will be reported
      if (typeofreducer ! = ='function') {
        throw new Error('Expected the reducer to be a function.')}... .// Finally, the createStore returns dispatch, SUBSCRIBE, getState, and other common apis
      return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
        [?observable]: observable
      };    
    }
    Copy the code

    The above code shows you what the createStore function does, which is to encapsulate the API and expose it to the user. Let’s take a look at the implementation of each API:

    Let’s look at the definition of a private variable

      let currentReducer = reducer // Reduce the reducer
      let currentState = preloadedState // This is the initial state passed in
      let currentListeners = [] // The current listener queue
      let nextListeners = currentListeners // Future listener queue
      let isDispatching = false // Indicates whether dispatch is underway
    Copy the code

    GetState: Used to get the state in the store. Redux does not allow the user to directly manipulate the state. To obtain the state, you need to use the getState API to obtain the state inside the store.

      function getState() {
        // If dispatch is in progress, the new state is being calculated. The current state is the old one, in order to ensure that the user can get the new one
        If dispatch is in progress, return the current state. If dispatch is in progress, return the current state
        if (isDispatching) {
          throw new Error(
            'You may not call store.getState() while the reducer is executing. ' +
              'The reducer has already received the state as an argument. ' +
              'Pass it down from the top reducer instead of reading it from the store.')}return currentState
      }
    Copy the code

    Subscribe: Redux provides the user with an API to listen for state changes. This is particularly important. Without this API exposed, React-Redux would be more implemented.

      function subscribe(listener) {
        // The listener is a callback when state changes. It must be a function
        if (typeoflistener ! = ='function') {
          throw new Error('Expected the listener to be a function.')}// If it is in dispatch, an error is reported. To ensure that state changes, the queue for the listener must also be up to date. So the listener is registered before the new state is calculated.
        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.')}// If the sign is registered, well, I don't think it is necessary. But if you think about it, it should prevent the user from calling the unlistened function multiple times.
        let isSubscribed = true
    
        / / this function is actually determine whether current listener queue and in the future, if not the same as the current assignment to the future, well, or is not very understanding why so, may be in order to achieve the result of data immutable, avoid press it into a new callback, in the current this callback listener queue also have
        ensureCanMutateNextListeners()
        // Press the callback into the future listener queue
        nextListeners.push(listener)
    
        // Registering a listener returns a function to cancel the listener
        return function unsubscribe() {
          // If this function has been called to cancel listening, return
          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.')}// The flag has been removed
          isSubscribed = false
    
          ensureCanMutateNextListeners()
            
          / / delete
          const index = nextListeners.indexOf(listener)
          nextListeners.splice(index, 1)}}Copy the code

    Dispatch: This corresponds to getState, where getState is read and Dispatch is write. If the redux state is changed, the reducer can only send an action to the reducer by sending a dispatch. The reducer calculates the new state according to the action, currentState, and its own internal implementation logic, so as to write data to the reducer.

      function dispatch(action) {
        // Action requires a simple Object, and a simple Object is an Object created through Object literals and new Object(). If not, an error is reported.
        if(! isPlainObject(action)) {throw new Error(
            'Actions must be plain objects. ' +
              'Use custom middleware for async actions.')}// The reducer internally uses the type attribute of the action to decide what logic to use to calculate state, so the type attribute is required.
        if (typeof action.type === 'undefined') {
          throw new Error(
            'Actions may not have an undefined "type" property. ' +
              'Have you misspelled a constant? ')}// If it is already in dispatch, an error is reported to avoid inconsistency
        if (isDispatching) {
          throw new Error('Reducers may not dispatch actions.')}CurrentState = currentState; currentState = currentState
        try {
          isDispatching = true
          currentState = currentReducer(currentState, action)
        } finally {
          isDispatching = false
        }
    
        // After the state is updated, it triggers all the registered callbacks. You should pay attention here, is triggered once oh! This point is understood, react-Redux some principles will be easier to understand.
        const listeners = (currentListeners = nextListeners)
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i]
          listener()
        }
    
        return action
      }
    Copy the code

    That’s the rough implementation of createStore. This function is less difficult and more of a gateway to redux. Let’s dig a little bit into the implementation of other code from this entry. Let’s start with combineReducers

  • combineReducers

    • This function is used to integrate multiple reducers, since createStore accepts only one Reducer.
    • This function is divided into two parts, the first part is to check the accuracy of the reducers passed in by the user. The second part is the logic to compute state. So let’s look at the first part and see why, but let’s focus on the second part, okay
    export default function combineReducers(reducers) {... . returnfunction combination(state = {}, action) {
        if (shapeAssertionError) {
          throw shapeAssertionError
        }
    
        if(process.env.NODE_ENV ! = ='production') {
          const warningMessage = getUnexpectedStateShapeWarningMessage(
            state,
            finalReducers,
            action,
            unexpectedKeyCache
          )
          if (warningMessage) {
            warning(warningMessage)
          }
        }
    
        // hasChanged to indicate whether the new state was calculated
        let hasChanged = false
        // This is where the new state is stored
        const nextState = {}
        Emmmm: traverse each reducer, transfer the action to it, and calculate the state
        for (let i = 0; i < finalReducerKeys.length; i++) {
          const key = finalReducerKeys[i]
          const reducer = finalReducers[key]
          const previousStateForKey = state[key]
          const nextStateForKey = reducer(previousStateForKey, action)
          // If a reducer does not return a new state, an error is reported
          if (typeof nextStateForKey === 'undefined') {
            const errorMessage = getUndefinedStateErrorMessage(key, action)
            throw new Error(errorMessage)
          }
          nextState[key] = nextStateForKey
          // This is where the new state is determinedhasChanged = hasChanged || nextStateForKey ! == previousStateForKey }// Based on this flag, decide whether to return the original state or the new state
        return hasChanged ? nextState : state
      }
    }
    Copy the code

    The integration process is to store all reducers in one object. When you dispatch an action, The state of each reducer is calculated by traversing each reducer. The optimization used is to determine whether the old and new state have changed after traversing each reducer, and decide whether to return the old state or the new state. The resulting data structure of state is similar to the data structure of the reducer, that is, the key is the reducer name, and the value is the corresponding reducer value. This part is actually not that hard. Moving on, let’s look at implementing applyMiddleware

  • applyMiddleware

    This section is designed to extend the functionality of Redux. Because the original function of Redux is to manipulate state, to manage state. If we need to extend some functions based on the requirements, we need to use middleware written by others or our own middleware to meet the requirements. For example, when launching a dispatch, we don’t want to manually print out the action from console.log for debugging purposes. In this case, we can write a Logger middleware to automatically print out the action from each dispatch, which is easy for us to test. For example, if we want to handle asynchronous actions, we can use middleware such as Redux-Thunk and Redux-Saga. In short, this function provides us with infinite possibilities.

    Let’s look at the code bit by bit:

    export default function applyMiddleware(. middlewares) {
      return createStore= >(... args) => {conststore = createStore(... args) .... . . return { ... store, dispatch } }Copy the code

    As an overview, notice that applyMiddleware accepts an indefinite amount of middleware and then returns a function that takes a creatStore as an argument and returns a function. Remember when we were at creatStore? There’s a scene there

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

    When enhancer is not empty and is a function, it executes the function and returns it as the value returned by the creatStore. And what is this enhancer? Look at redux’s documentation:

    const store = createStore(reducers, applyMiddleware(a, b, c));
    Copy the code

    Oh! Enhancer is the result of applyMiddleware, which is a creatStore => (… Args) = {}. Let’s look at the enhancer code:

      return createStore= >(... args) => {conststore = createStore(... args)// 1. Maybe some students will be a little blind to see this at first, I also felt strange at that time, the logic of this dispatch is not right
        // We also pass the dispatch as an argument to middleware, indicating that the logic for using the Dispatch is this
        // Dispatch = compose(... chain)(store.dispatch)
        // That's ok. We know from the scope chain that when we call the dispatch in the middleware, we are actually calling the dispatch, not the logic declared at the beginning
        // This dispatch is already packaged in compose. The logic is pretty clear at this point
        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) }// 2. How is the middleware connected in compose?
        // First a simple middleware format: store => next => action => {}
        // This line of code is a function that passes store and gets next => action => {}
        const chain = middlewares.map(middleware= > middleware(middlewareAPI))
    
        // This line of code is actually split into two lines
        // const composeRes = compose(... chain);
        // dispatch = composeRes(store.dispatch);
        For example, next => action => {} for compose, next => action => {} for compose args) => f(g(b(... Args)))
        // The second line is passed through the store.dispatch, which is the next argument to the last next => action => {}
        // After incoming (... args) => f(g(b(... (args)), store.dispacth is passed as b's next, and the result of b's action => {} is passed as
        // next of g is passed in, and so on. So finally dispatch is output as the Dispatch property of the store with the middleware, and when the user calls dispatch, the middleware will do one by one
        // After the logic is executed, the execution is given to the next one until the original store.dispacth, and finally the new state is calculateddispatch = compose(... chain)(store.dispatch)return {
          ...store,
          dispatch
        }
      }
    Copy the code

    If you follow the notes above, you should be able to understand how enhancer works. So to summarize, the enhancer receives a creatStore, it creates a store internally, and then it augments that store, and it augments that store at the Dispatch. For example, compose a dispatch chain in the form of: **[middleware 1, middleware 2,……, middleware N, store.dispatch]**, The enhanced Dispatch is then exposed to the user as the store’s new Dispatch. In this case, the user will execute each middleware in turn when dispatching each middleware. After the current one is executed, the execution authority will be transferred to the next one until the new state is calculated in the reducer.

conclusion

Redux online to explain the source code a lot, I this is also one of them, mainly after my personal learning source code, a record way, deepen their impression, but also to forget after can quickly review. Redux is actually not difficult to implement, but it is the essence of the mind. The programmer’s coding ability is a rigid need, but the design idea is to borrow other mountain jade, to attack the stone. Standing on the shoulders of giants to see the distance, I hope to read more source code of others, in can understand the principle of better use at the same time, I can also create a good wheel. Thank you for your time. In addition, with source address: github.com/Juliiii/sou… Welcome to star and Fork, and welcome to discuss with me.