1. ホーム
  2. ジャバスクリプト

欲しいもの - reduxのソースコード解析

2022-03-16 14:45:55

皆さんこんにちは、今日はredux(v3.6.0)のソースコード解析についてお届けします〜。

まず、reduxのgithubアドレスです。 クリックミー

次に、私たちのプロジェクトにおけるreduxの簡単な使い方について見てみましょう。

注:この例はreactで使用されていますが、もちろんreduxはreactだけでなく、市場にある他のほとんどのフレームワークで動作しますし、そこが少し欠点でもあります

最初のステップは、ストアを作成することです。

import React from 'react'
import { render } from 'react-dom'
// First we must import the createStore method in redux to create the store
// import the applyMiddleware method to use the middleware
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
// import redux's middleware thunk
import thunk from 'redux-thunk'
// import redux's middleware createLogger
import { createLogger } from 'redux-logger'
// We must also define our own reducer function to access the new state based on the action we passed in
import reducer from '. /reducers'
import App from '. /containers/App'

// Create an array of middleware
const middleware = [ thunk ]
if (process.env.NODE_ENV ! == 'production') {
  middleware.push(createLogger())
}
// Call the createStore method to create the store, passing in the reducer and the function that applies the middleware, respectively
const store = createStore(
  reducer,
  applyMiddleware(... . middleware)
)
// Pass the store as an attribute so that you can get the store instance in each child component and use the store's methods
render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)


次に、reducer がどのように定義されているかを見てみましょう。

// First we import the combineReducers method in redux
import { combineReducers } from 'redux'
// Import actions, this is not required but recommended
import {
  SELECT_REDDIT, INVALIDATE_REDDIT,
  REQUEST_POSTS, RECEIVE_POSTS
} from '... /actions'

// The next two methods selectedReddit, postsByReddit are the reducer methods
// The reducer method is responsible for returning the new state according to the type of action passed in, here you can pass in the default state
const selectedReddit = (state = 'reactjs', action) => {
  switch (action.type) {
    case SELECT_REDDIT:
      return action.reddit
    default:
      return state
  }
}

const posts = (state = {
  isFetching: false,
  didInvalidate: false,
  items: []
}, action) => {
  switch (action.type) {
    case INVALIDATE_REDDIT:
      return {
        ... . state,
        didInvalidate: true
      }
    case REQUEST_POSTS:
      return {
        ... . state,
        isFetching: true,
        didInvalidate: false
      }
    case RECEIVE_POSTS:
      return {
        ... . state,
        isFetching: false,
        didInvalidate: false,
        items: action.posts,
        lastUpdated: action.receivedAt
      }
    default:
      return state
  }
}

const postsByReddit = (state = { }, action) => {
  switch (action.type) {
    case INVALIDATE_REDDIT:
    case RECEIVE_POSTS:
    case REQUEST_POSTS:
      return {
        ... . state,
        [action.reddit]: posts(state[action.reddit], action)
      }
    default:
      return state
  }
}

// Finally, we combine all the reducer methods into one method, the rootReducer method, via the combineReducers method
const rootReducer = combineReducers({
  postsByReddit,
  selectedReddit
})
// Export this rootReducer method
export default rootReducer


次のステップでは、アクションの定義を見ていきます。アクションは、必須属性の type と非必須属性の payload を持つオブジェクトです。type はアクションの種類を表し、状態を修正するアクションの意図を指定し、一方 payload はリデューサが使用する追加データを渡す方法です。

export const REQUEST_POSTS = 'REQUEST_POSTS'
export const RECEIVE_POSTS = 'RECEIVE_POSTS'
export const SELECT_REDDIT = 'SELECT_REDDIT'
export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT'

export const selectReddit = reddit => ({
  type: SELECT_REDDIT,
  reddit
})
export const invalidateReddit = reddit => ({
  type: INVALIDATE_REDDIT,
  reddit
})

export const requestPosts = reddit => ({
  type: REQUEST_POSTS,
  reddit
})

export const receivePosts = (reddit, json) => ({
  type: RECEIVE_POSTS,
  reddit,
  posts: json.data.children.map(child => child.data),
  receivedAt: Date.now()
})

const fetchPosts = reddit => dispatch => {
  dispatch(requestPosts(reddit))
  return fetch(`https://www.reddit.com/r/${reddit}.json`)
    .then(response => response.json())
    .then(json => dispatch(receivePosts(reddit, json)))
}

const shouldFetchPosts = (state, reddit) => {
  const posts = state.postsByReddit[reddit]
  if (!posts) {
    return true
  }
  if (posts.isFetching) {
    return false
  }
  return posts.didInvalidate
}

export const fetchPostsIfNeeded = reddit => (dispatch, getState) => {
  if (shouldFetchPosts(getState(), reddit)) {
    return dispatch(fetchPosts(reddit))
  }
}


これはreduxの最もシンプルな使い方なので、reduxのソースコードでどのように実装されているのか見てみましょう。

まず、redux プロジェクト全体のディレクトリ構造を見てみましょう。ここから、redux プロジェクトのソースコードが実は比較的シンプルであることがわかります。

エントリーファイルのindex.jsから始めましょう。このファイルは実質的には何も実装していませんが、reduxが提供する機能をエクスポートしているだけです。

// The entry file
// First introduce the appropriate module, the details of which will be analyzed later
import createStore from '. /createStore'
import combineReducers from '. /combineReducers'
import bindActionCreators from '. /bindActionCreators'
import applyMiddleware from '. /applyMiddleware'
import compose from '. /compose'
import warning from '. /utils/warning'

/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV ! == 'production', warn the user.
*/
function isCrushed() {}

if (
  process.env.NODE_ENV ! == 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name ! == 'isCrushed'
) {
  warning(
    'You are currently using minified code outside of NODE_ENV === \'production\'. ' +
    ' + 'This means that you are running a slower development build of Redux.
    'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin
    ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' +
    ' to ensure you have the correct code for your production build.'
  )
}
// Export the appropriate functions
export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}


その直後、reduxの重要なファイルであるcreateStore.jsを見てみましょう。

// The file that creates the store provides all the built-in functionality of the store in redux, and is one of the more important files in redux

// First introduce the appropriate module
import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'

/**
 * These are private action types reserved by Redux.
 * For any unknown actions, you must return the current state.
 * If the current state is undefined, you must return the initial state.
 * Do not reference these action types directly in your code.
 */

 // Defines an ActionType for internal use
export const ActionTypes = {
  INIT: '@@redux/INIT'
}

/**
 * Creates a Redux store that holds the state tree.
 * The only way to change the data in the store is to call `dispatch()` on it.
 *There should only be a single store.
 * There should only be a single store in your app.
 * To specify how different parts of the state tree respond to actions, you may combine several reducers
 To specify how different * parts of the state tree respond to actions, you may combine several reducers * into a single reducer function by using `combineReducers`.
 * @param
 * @param {Function} reducer A function that returns the next state tree, given
 * the current state tree and the action to handle.
 *
 * @param {any} [preloadedState] The initial state. You may optionally specify it
 You may optionally specify it * to hydrate the state from the server in universal apps, or to restore a
 You may optionally specify it * to hydrate the state from the server in universal apps, or to restore a * previously serialized user session.
 * If you use `combineReducers` to produce the root reducer function, this must be
 If you use `combineReducers` to produce the root reducer function, this must be * an object with the same shape as `combineReducers` keys.
 *
 * @param {Function} [enhancer] The store enhancer.
 You may optionally specify it * to enhance the store with third-party capabilities such as middleware,
 You may optionally specify it * to enhance the store with third-party capabilities such as middleware, * time travel, persistence, etc. The only store enhancer that ships with Redux
 The only store enhancer that ships with Redux * is `applyMiddleware()`.
 The only store enhancer that ships with Redux * is `applyMiddleware()`.
 * @returns {Store} A Redux store that lets you read the state, dispatch actions
 * and subscribe to changes.
 */

// Export the method that creates the store
// This method takes three parameters, the reducer, the preloaded state, and the enhancement function enhancer
export default function createStore(reducer, preloadedState, enhancer) {
  // adjust the parameters, if no preloaded state is passed and the second parameter is a function, then the second parameter is the enhancer function
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
  // determine that enhancer must be a function
  if (typeof enhancer ! == 'undefined') {
    if (typeof enhancer ! == 'function') {
      throw new Error('Expec
  function subscribe(listener) {
    // The listener must be a function
    if (typeof listener ! == 'function') {
      throw new Error('Expected listener to be a function.')
    }
    // Declare a variable to mark whether it has been subscribed, which is cached via a closure
    let isSubscribed = true
    // Create a copy of the current currentListeners and assign it to nextListeners
    ensureCanMutateNextListeners()
    // push the listener function to nextListeners
    nextListeners.push(listener)
    // return a function to unlisten
    // The principle is simple: remove the current function from the array, using the array's splice method
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  /* * Dispatches an action.
   * Dispatches an action. It is the only way to trigger a state change.
   *It is the only way to trigger a state change.
   It is the only way to trigger a state change. * The `reducer` function, used to create the store, will be called with the
   The `reducer` function, used to create the store, will be called with the * current state tree and the given `action`. Its return value will
   Its return value will * be considered the * *next** state of the tree, and the change listeners
   Its return value will * be considered the * *next** state of the tree, and the change listeners * will be notified.
   *The base implementation only supports plain object.
   * The base implementation only supports plain object actions.
   * If you want to dispatch a Promise, an Observable, a thunk, or something else, you need to
   If you want to dispatch a Promise, an Observable, a thunk, or something else, you need to * wrap your store creating function into the corresponding middleware.
   For * example, see the documentation for the `redux-thunk` package.
   Even the * middleware will eventually dispatch plain object actions using this method.
   Even the * middleware will eventually dispatch plain object actions using this method.
   Even the * middleware will eventually dispatch plain object actions using this method. * @param {Object} action A plain object representing "what changed". It is
   It is * a good idea to keep actions serializable so you can record and replay user
   It is * a good idea to keep actions serializable so you can record and replay user * sessions, or use the time travelling `redux-devtools`. An action must have
   An action must have * a `type` property which may not be `undefined`. It is a good idea to use
   It is a good idea to use * string constants for action types.
   It is a good idea to use * string constants for action types.
   It is a good idea to use * string constants for action types. * @returns {Object} For convenience, the same action object you dispatched.
   It is a good idea to use * string constants for action types.
   * Note that, if you use a custom middleware, it may wrap `dispatch()` to
   * return something else (for example, a Promise you can await).
   */

  // An action is dispatched in redux to trigger a change to the state of the store
  // The argument is an action
  function dispatch(action) {
    // Here we determine if the action is a pure object, and throw an error if it is not
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        ' + 'Use custom middleware for async actions.'
      )
    }
    // The action must have a type attribute, otherwise it throws an error
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        ' + 'Have you misspelled a constant?'
      )
    }
    // If the last dispatch has not finished, you cannot continue dispatching the next one
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      // Set isDispatching to true to indicate the start of the current dispatch
      isDispatching = true
      // Use the incoming reducer function to process the state and action and return the new state
      // It is recommended not to modify the currentState directly
      currentState = currentReducer(currentState, action)
    } finally {
      // end of current dispatch
      isDispatching = false
    }
    // After each dispatch, execute the listener function in the listener queue
    // assign nextListeners to currentListeners to ensure that the next execution of ensureCanMutateNextListeners will recopy a new copy
    // A simple and brutal for loop execution
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    // Finally return the action
    return action
  }

  /*
   * Replaces the reducer currently used by the store to calculate the state.
   *You might need this if your app implements code splitting and you want to calculate the state.
   * You might need this if your app implements code splitting and you want to
   * You might also need this if yo

次に combineReducers.js を見てみましょう。これは通常、reducer メソッドをマージするために使用します。

このファイルは、複数のリデューサをマージし、ルートリデューサを返すために使用されます。

ストアには1つのレデューサー機能しかないため、モジュール分割を行う場合はこのメソッドが必要です。

// Start by importing the appropriate functions
import { ActionTypes } from '. /createStore'
import isPlainObject from 'lodash/isPlainObject'
import warning from '. /utils/warning'

// Get the error message of UndefinedState
function getUndefinedStateErrorMessage(key, action) {
  const actionType = action && action.type
  const actionName = (actionType && `"${actionType.toString()}"`) || 'an action'

  return (
    `Given action ${actionName}, reducer "${key}" returned undefined. ` +
    `To ignore an action, you must explicitly return the previous state.
    ` If you want this reducer to hold no value, you can return null instead of undefined.`
  )
}

function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
  // Get all keys of reducers
  const reducerKeys = Object.keys(reducers)
  const argumentName = action && action.type === ActionTypes.INIT ?
    'preloadedState argument passed to createStore' :
    'previous state received by the reducer'
  // When the reducers object is an empty object, return a warning text
  if (reducerKeys.length === 0) {
    return (
      'Store does not have a valid reducer. make sure the argument passed ' +
      'to combineReducers is an object whose values are reducers.'
    )
  }
  // state must be an object
  if (!isPlainObject(inputState)) {
    return (
      `The ${argumentName} has unexpected type of "` +
      ({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
      `". Expected argument to be an object with the following ` +
      `keys: "${reducerKeys.join('", "')}"`
    )
  }
  // determine if there are keys in the state that the reducer does not have, because when redux divides the state into modules, it is based on the reducer
  const unexpectedKeys = Object.keys(inputState).filter(key =>
    !reducers.hasOwnProperty(key) &&
    !unexpectedKeyCache[key]
  )

  unexpectedKeys.forEach(key => {
    unexpectedKeyCache[key] = true
  })

  if (unexpectedKeys.length > 0) {
    return (
      `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
      `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
      ` Expected to find one of the known reducer keys instead: ` +
      `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
    )
  }
}
// assertReducerShape function to check if the reducer will return an undefined when it encounters a position action, and throw an error if it does
// accept a reducers object
function assertReducerShape(reducers) {
  // Iterate over the reducers object
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    // Get the value returned by the reducer function when the state is undefined and the actionType is the initial default type
    const initialState = reducer(undefined, { type: ActionTypes.INIT })
    // If the value is undefined, throw an error because the initial state should not be undefined
    if (typeof initialState === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
        `If the state passed to the reducer is undefined, you must ` +
        ` + `explicitly return the initial state.
        If you don't want to set a value for this reducer, ` + `you can use null instead of
        ` you can use null instead of undefined.`
      )
    }
    // When an action is encountered that you don't know about, the reducer cannot return undefined either, otherwise it will also throw an error
    const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
    if (typeof reducer(undefined, { type }) === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined when
  })
}

/**
 * Turns an object whose values are different reducer functions, into a single
 * It will call every child reducer, and gather their results
 It will call every child reducer, and gather their results * into a single state object, whose keys correspond to the keys of the passed
 * reducer functions.
 It will call every child reducer, and gather their results * into a single state object, whose keys correspond to the keys of the passed * reducer functions.
 It will call every child reducer, and gather their results * into a single state object, whose keys correspond to the keys of the passed * reducer functions.
 * reducer functions that need to be combined into one.
 One handy way to obtain * it is to use ES6 `import * as reducers` syntax.
 * Instead, they should return their initial state
 reducers may never return * undefined for any action.
 * unrecognized action.
 * @returns {Function}
 * @returns {Function} A reducer function that invokes every reducer inside the
 * passed object, and builds a state object with the same shape.
 */

// Export the combineReducers method, accepting a reducers object as an argument
export default function combineReducers(reducers) {
  // Get the key value of the reducers object
  const reducerKeys = Object.keys(reducers)
  // Define a final reducers object to be returned
  const finalReducers = {}
  // Iterate through the keys of this reducers object
  for (let i = 0; i < reducerKeys.length; i++) {
    // cache each key value
    const key = reducerKeys[i]

    if (process.env.NODE_ENV ! == 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }
    // If the value of the corresponding key is a function, the function is cached in finalReducers
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  // Get all the key values of the finalReducers and cache them in the variable finalReducerKeys
  const finalReducerKeys = Object.keys(finalReducers)

  let unexpectedKeyCache
  if (process.env.NODE_ENV ! == 'production') {
    unexpectedKeyCache = {}
  }
  // Define a variable to cache the error object
  let shapeAssertionError
  try {
    // Do the error handling, see the assertReducerShape method later for details
    // mostly just detects that
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  return function combination(state = {}, action) {
    // Throw an error if there is one
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV ! == 'production') {
      // Get the warning message
      const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
      if (warningMessage) {
        warning(warningMessage)
      }
    }
    // Define a variable to indicate whether the state has been changed
    let hasChanged = false
    // Define a variable to cache the changed state
    const nextState = {}
    // Start iterating through the finalReducerKeys
    for (let i = 0; i < finalReducerKeys.length; i++) {
      // Get the key value of a valid reducer
      const key = finalReducerKeys[i]
      // Get the corresponding reducer function based on the key value
      const reducer = finalReducers[key]
      // Get the corresponding state module based on the key value
      const previousStateForKey = state[key]
      // Execute the reducer function and get the state of the corresponding module
      const nextStateForKey = reducer(previousStateForKey, action)
      // If the state obtained is undefined, an error will be thrown
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // Assign the new state to the module corresponding to the new state, with the key being the key of the current reducer
      nextState[key] = nextStateForKey
      // Read if the state has changed
      hasChanged = hasChanged || nextStateForKey ! == previousStateForKey
    }
    // If the state has changed, return the new state, otherwise return the original state
    return hasChanged ? nextState : state
  }
}




次に bindActionCreators.js ファイルを見てみましょう。

まず、actionCreators からですが、これは単純にアクションを作成するためのメソッドです。redux のアクションはオブジェクトですが、このオブジェクトを作成するためによく関数を使用するので、これらの関数が actionCreators となります。

そして、このファイルが行うことは、バインディングに基づいてactionCreatorを自動的にディスパッチすることです。


import warning from '. /utils/warning'
// For each actionCreator method, the execution will result in an action
// The bindActionCreator method returns a method that automatically executes a dispatch
function bindActionCreator(actionCreator, dispatch) {
  return (.. .args) => dispatch(actionCreator(actionCreator(.. .args))
}

/**
 * Turns an object whose values are action creators, into an object with the
 ...args. } /** * Turns an object whose values are action creators, into an object with the * same keys, but with every function wrapped into a `dispatch` call so they
 * This is just a convenience method, as you can call
 This is just a convenience method, as you can call * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
 For convenience, you can also pass * `store.
 * For convenience, you can also pass a single function as the first argument,
 * and get a function in return.
 *
 * @param {Function|Object} actionCreators An object whose values are action
 * One handy way to obtain it is to use ES6 `import * as` syntax.
 * You may also pass a single function.
 *You may also pass a single function.
 You may also pass a single function. * @param {Function} dispatch The `dispatch` function available on your Redux
 * store.
 * @param
 * @returns {Function|Object} The object mimicking the original object, but with
 * every action creator wrapped into the `dispatch` call.
 * function as `actionCreators`, the return value will also be a single
 * function.
 */
// Expose this bindActionCreators method externally
export default function bindActionCreators(actionCreators, dispatch) {
  // If the actionCreators parameter is a function, call the bindActionCreator method directly
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
  // Error handling
  if (typeof actionCreators ! == 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
      `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"? `
    )
  }
  // If actionCreators is an object, then get the keys in the object
  const keys = Object.keys(actionCreators)
  // Define a cache object
  const boundActionCreators = {}
  // iterate through each key of actionCreators
  for (let i = 0; i < keys.length; i++) {
    // get each key
    const key = keys[i]
    // Get the specific actionCreator method based on each key
    const actionCreator = actionCreators[key]
    // If actionCreator is a function, call the bindActionCreator method directly, caching the returned anonymous function into the boundActionCreators object
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    } else {
      warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'. `)
    }
  }
  // Finally the boundActionCreators object is returned
  // Once the user gets this object, he can take out the value corresponding to each key in the object, i.e. each anonymous function, and execute the anonymous function to implement the dispatch function
  return boundActionCreators
}



次に、reduxに無限の可能性を与えてくれるapplyMiddleware.jsというファイルを見てみましょう。なぜそうなるのか?下にスクロールするとわかります。

// The code logic in this file is actually quite simple
// First import the compose function, which we'll analyze in more detail in a moment
import compose from '. /compose'

/**
 * Creates a store enhancer that applies middleware to the dispatch method * of the Redux store.
 * This is handy for a variety of tasks, such as expressing
 This is handy for a variety of tasks, such as expressing * asynchronous actions in a concise manner, or logging every action payload.
 *See
 * See `redux-thunk` package as an example of the Redux middleware.
 *See the `redux-thunk` package as an example of the Redux middleware.
 * Because middleware is potentially asynchronous, this should be the first
 * This should be the first store enhancer in the composition chain.
 This should be the first * store enhancer in the composition chain.
 * Note that each middleware will be given the `dispatch` and `getState` functions
 * as named arguments.
 * @param {.
 * @param {... Function} middlewares The middleware chain to be applied.
 * @returns {Function} A store enhancer applying the middleware.
 */
 // Next, we export the applyMiddleware method, which is often used as an enhancement parameter in createStore
export default function applyMiddleware(.... .middlewares) {
  // first return an anonymous function, did you find this function is very similar to createStore?
  // Yes, it's actually the same createStore we saw before
  return (createStore) => (reducer, preloadedState, enhancer) => {
    // First create a store with the original createStore and cache it
    const store = createStore(reducer, preloadedState, enhancer)
    // Get the original dispatch method in store
    let dispatch = store.dispatch
    // define an array of execution chains
    let chain = []
    // cache the original store getState and dispatch methods
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    // execute each middleware function and pass middlewareAPI as a parameter to get an array of execution chains
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    // Pass the execution chain array into the compose method and immediately execute the returned method to get the final wrapped dispatch
    // The process is simply that each middleware takes a store.dispatch method, wraps it based on that method, and returns a new dispatch
    // This new dispatch is then passed as an argument to the next middleware function, which is then wrapped. Keep looping through this process until you end up with a final dispatch
    dispatch = compose(... .chain)(store.dispatch)
    // return a store object and override the old dispatch method with the new dispatch method
    return {
      ... .store,
      dispatch
    }
  }
}




ここまででREDUXの大部分を見てもらったので、最後に上記のファイルで使われているcomposeメソッドがどのように実装されているかを見てみましょう。

compose.jsを開くと、実際にはes5の配列のreduceメソッドを使って、このような効果を実現していることがわかります。

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 The rightmost function can take multiple arguments as it provides the signature for * the resulting composite function. * @param {. Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * For example, compose(f, g, h) is identical to doing
 For example, compose(f, g, h) is identical to doing * (.... .args) => f(g(h(. .args))).
 */

export default function compose(.. .funcs) {
  // Determine if the function array is empty
  if (funcs.length === 0) {
    return arg => arg
  }
  // If the function array has only one element, execute it directly
  if (funcs.length === 1) {
    return funcs[0]
  }

  // Otherwise, execute each middleware function using the reduce method, taking the return of the previous function as an argument to the next function
  return funcs.reduce((a, b) => (. .args) => a(b(. .args)))
}


ははは、以上、本日ご紹介したreduxのソースコード解析でした〜。