Redux source code series - Redux thunk source code analysis

Posted by Hillu on Wed, 30 Oct 2019 20:01:23 +0100

#Understand what middleware is

https://github.com/zp1112/blog/issues/11

thunk Middleware

  • Let's see how middleware works.
  • index.js
import {Provider} from 'react-redux'
import storeData from './reducerfile/index.js'
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'

const store = createStore(storeData, applyMiddleware(thunk))

ReactDOM.render(
    <Provider store={store}> 
        <App/>
    </Provider>, 
    document.getElementById('root')
)
  • action.js
export const fetchData = () => (dispatch) => {
  dispatch(change(9999, 6666))
  setTimeout(() => {
    getApi().then(data => {
      console.log(data)
      dispatch(change(data.age, data.text))
    })
  },1000)
}

Discovery is the action of the original simple object, injected with the rewritten dispatch, and then other operations can be done in this action.
  • After redux uses middleware, it rewrites dispatch.
Invoke in component: dispatch(action) = middleware(store)(store.dispatch)(action) / /
  • When using applyMiddleware, the implementation rewrites the dispatch code
  • The main function of applyMiddleware is to pass it to the middleware store, dispatch, and rewrite dispatch according to different middleware.
export function applyMiddleware(middleware) { // 
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = store.dispatch

    const midApi = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args) // Native dispatch
    }
    dispatch = middleware(midApi)(store.dispatch) //Note that when the dispatch is rewritten, the native dispatch is also saved and transferred to the middleware.
    return {
      ...store,
      dispatch //The dispatch here has been rewritten
    }
  }
}
  • Middleware, implementation principle
const thunk =(store) => next => action =>{}
// The dispatch taken here is the rewritten dispatch. next is equivalent to the original dispatch.
const thunk = ({ dispatch, getState }) => next => action => {
  if (typeof action === 'function') {
    return action(dispatch, getState)
//The dispatch here is equivalent to middle(store)(store.dispatch)(action)
  }
  return next(action) //If the return is not a function, it will be directly dispatch ed when there is no middleware.
// Note that next here is equivalent to the native store.dispatch
}

From the above, we can know that the middleware needs to obtain the store.
So applyMiddleware

function middleware({midApi}){
    return function(dispatch){
        return function(action){
            action(dispatch)
        }
    }
}
function action(dispatch){
    dispatch(action)
    const data = await getData()
    dispatch({type: 'DO',payload: data})
    
}
// action is function
dispatch(action)  // Equivalent to
middleware(midApi)(dispathc)(action()) // Execute action(dispatch)
//Because dispath has been reassigned when using middleware.

export function applyMiddleware(middleware) {
    // 
    return createStore => (...args) => {
    // The key step is to get a store object through createStore.
   // The store object has dispatch and getState methods
      const store = createStore(...args)
      let dispatch = store.dispatch
  
      const midApi = {
        getState: store.getState,
        dispatch: (...args) => dispatch(...args)
      }

      // To execute dispatch later is to execute middleware(midApi)(store.dispatch)
      dispatch = middleware(midApi)(store.dispatch)
      return {
        ...store,
        dispatch
      }
    }
  }


thunk(midApi)(dispathc)(action()) // Execute action(dispatch)

Key:

  • applyMiddleWare gets the createStore method
  • The createStore() execution returns an object containing the dispatch and getState
    So dispathc is reassigned to middleware(midApi)(store.dispatch).
dispatch(action) //Equivalent to
middleware(midApi)(store.dispatch)(action)

Why can applyMiddleWare call the createStore method and createStore() returns an object? What does the returned object contain?
See the source code of createStore

  • Part of the source code of createStore
export default function createStore(reducer, preloadedState, enhancer) {
 
  let currentReducer = reducer 
  let currentState = preloadedState /
  let isDispatching = false 

// So above
  export {
    dispatch, 
    subscribe,
    getState, 
    replaceReducer,
    [$$observable]: observable
  }
  
}



The dispatch and getState methods are exposed in the source code of createStore, so the dispatch and getState can be obtained in applyMiddleware.
Why can I get the createStore method

export default function createStore(reducer, preloadedState, enhancer) {
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function.'
    )
  }

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

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    
    // The enhancer here is the apple middleware above
    // The createStore called by the enhance function is passed in.
    return enhancer(createStore)(reducer, preloadedState)
  }
 .........
}

Topics: github React