Redux系列x:源碼分析

寫在前面

redux的源碼很簡潔,除了applyMiddleware比較繞難以理解外,大部分仍是比較容易理解的。html

這裏假設讀者對redux有必定了解,就不科普redux的概念和API啥的啦,這部分建議直接看官方文檔react

此外,源碼解析的中文批註版已上傳至github,可點擊查看。本文相關示例代碼,可點擊查看git

源碼解析概覽

將redux下載下來,而後看下他的目錄結構。github

npm install redux

這裏咱們須要關心的主要是src目錄,源碼解析須要關心的文件都在這裏面了express

  • index.js:redux主文件,主要對外暴露了幾個核心API
  • createStore.jscreateStore 方法的定義
  • utils:各類工具方法,其中applyMiddleware、combineReducers、bindActionCreators 爲redux的幾個核心方法,剩餘的pick、mapValue、compose爲普通的工具函數
➜  src git:(master) ✗ tree
.
├── createStore.js
├── index.js
└── utils
    ├── applyMiddleware.js
    ├── bindActionCreators.js
    ├── combineReducers.js
    ├── compose.js
    ├── isPlainObject.js
    ├── mapValues.js
    └── pick.js

源碼解析:index.js

超級簡單,暴露了幾個核心API,沒了npm

mport createStore from './createStore';
import combineReducers from './utils/combineReducers';
import bindActionCreators from './utils/bindActionCreators';
import applyMiddleware from './utils/applyMiddleware';
import compose from './utils/compose';

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
};

源碼解析:createStore.js

直接貼上源代碼,並進行簡單註解。看下redux.createStore(reducer, initialState)調用的文檔說明,基本就可以看懂下面代碼了。redux

特別強調:雖然在幾個文件裏,createStore.js的代碼行數是最多的,但倒是最容易讀懂的。下面幾點比較關鍵數組

  1. redux.createStore(reducer, initialState) 傳入了reducer、initialState,並返回一個store對象。
  2. store對象對外暴露了dispatch、getState、subscribe方法
  3. store對象經過getState() 獲取內部狀態
  4. initialState爲 store 的初始狀態,若是不傳則爲undefined
  5. store對象經過reducer來修改內部狀態
  6. store對象建立的時候,內部會主動調用dispatch({ type: ActionTypes.INIT });來對內部狀態進行初始化。經過斷點或者日誌打印就能夠看到,store對象建立的同時,reducer就會被調用進行初始化。
import isPlainObject from './utils/isPlainObject';

/**
 * 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.
 */
// 初始化的時候(redux.createStore(reducer, initialState)時),傳的action.type 就是這貨啦
export var 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 in your app. 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 {Function} reducer A function that returns the next state tree, given
 * the current state tree and the action to handle.
 *
 * @param {any} [initialState] The initial state. 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
 * an object with the same shape as `combineReducers` keys.
 *
 * @returns {Store} A Redux store that lets you read the state, dispatch actions
 * and subscribe to changes.
 */
export default function createStore(reducer, initialState) {
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.');
  }

  var currentReducer = reducer;
  var currentState = initialState;
  var listeners = [];
  var isDispatching = false;

  /**
   * Reads the state tree managed by the store.
   *
   * @returns {any} The current state tree of your application.
   */
  // 這個方法沒什麼好講的,返回當前的state
  function getState() {
    return currentState;
  }

  /**
   * Adds a change listener. It will be called any time an action is dispatched,
   * and some part of the state tree may potentially have changed. You may then
   * call `getState()` to read the current state tree inside the callback.
   *
   * @param {Function} listener A callback to be invoked on every dispatch.
   * @returns {Function} A function to remove this change listener.
   */
  // 很常見的監聽函數添加方式,當store.dispatch 的時候被調用
  // store.subscribe(listener) 返回一個方法(unscribe),能夠用來取消監聽
  function subscribe(listener) {
    listeners.push(listener);
    var isSubscribed = true;

    return function unsubscribe() {
      if (!isSubscribed) {
        return;
      }

      isSubscribed = false;
      var index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  }

  /**
   * Dispatches an action. It is the only way to trigger a state change.
   *
   * The `reducer` function, used to create the store, will be called with the
   * current state tree and the given `action`. 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 actions. 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.
   *
   * @param {Object} action A plain object representing 「what changed」. 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
   * a `type` property which may not be `undefined`. It is a good idea to use
   * string constants for action types.
   *
   * @returns {Object} For convenience, the same action object you dispatched.
   *
   * Note that, if you use a custom middleware, it may wrap `dispatch()` to
   * return something else (for example, a Promise you can await).
   */
  // 如下狀況會報錯
  // 1. 傳入的action不是一個對象
  // 2. 傳入的action是個對象,可是action.type 是undefined
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      );
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      );
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.');
    }

    try {
      isDispatching = true;
      // 就是這一句啦, 將 currentState 設置爲 reducer(currentState, action) 返回的值
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    // 若是有監聽函數,就順序調用
    listeners.slice().forEach(listener => listener());

    // 最後,返回傳入的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
   * load some of the reducers dynamically. You might also need this if you
   * implement a hot reloading mechanism for Redux.
   *
   * @param {Function} nextReducer The reducer for the store to use instead.
   * @returns {void}
   */
  function replaceReducer(nextReducer) {
    currentReducer = nextReducer;
    dispatch({ type: ActionTypes.INIT });
  }

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  //
  // redux.createStore(reducer, initialState) 的時候, 內部會 本身調用 dispatch({ type: ActionTypes.INIT });
  // 來完成state的初始化
  dispatch({ type: ActionTypes.INIT });

  // 返回的就是這個東東了,只有四個方法
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer
  };
}

源碼解析:combineReducers.js

redux.combineReducers(reducerMap) 的做用在於合併多個reducer函數,並返回一個新的reducer函數。所以能夠看到,combineReducers 返回了一個函數,而且該函數的參數一樣是state、reducer。session

能夠先看僞代碼感覺下,最終 store.getState() 返回的state,大概會是這麼個樣子{todos: xx, filter: xx}。簡單的說,state被拆分紅了兩份,TodoReducer的返回值賦值給了state.todos,FilterReducer的返回值賦值給了state.filterapp

function TodoReducer(state, action) {}
function FilterReducer(state, action) {}

var finalReducers = redux.combineReducers({
    todos: TodoReducer,
    filter: FilterReducer
});

一樣是直接上註解後的代碼,記住幾個關鍵就差很少了:

  1. combineReducers(reducerMap) 傳入一個對象,並返回一個全新的reducer。調用方式跟跟普通的reducer同樣,也是傳入state、action。
  2. 經過combineReducers,對 store 的狀態state進行拆分,
  3. reducerMap的key,就是 state 的key,而 調用對應的reducer返回的值,則是這個key對應的值。如上面的例子,state.todos == TodoReducer(state, action)
  4. redux.createStore(finalReducers, initialState) 調用時,一樣會對 state 進行初始化。這個初始化跟經過普通的reducer進行初始化沒多大區別。舉例來講,若是 initialState.todos = undefined,那麼 TodoReducer(state, action) 初始傳入的state就是undefined;若是initialState.todos = [],那麼 TodoReducer(state, action) 初始傳入的state就是[];
  5. store.dispatch(action),finalReducers 裏面,會遍歷整個reducerMap,依次調用每一個reducer,並將每一個reducer返回的子state賦給state對應的key。
import { ActionTypes } from '../createStore';
import isPlainObject from '../utils/isPlainObject';
import mapValues from '../utils/mapValues';
import pick from '../utils/pick';

/* eslint-disable no-console */

function getUndefinedStateErrorMessage(key, action) {
  var actionType = action && action.type;
  var actionName = actionType && `"${actionType.toString()}"` || 'an action';

  return (
    `Reducer "${key}" returned undefined handling ${actionName}. ` +
    `To ignore an action, you must explicitly return the previous state.`
  );
}

function getUnexpectedStateKeyWarningMessage(inputState, outputState, action) {
  var reducerKeys = Object.keys(outputState);
  var argumentName = action && action.type === ActionTypes.INIT ?
    'initialState argument passed to createStore' :
    'previous state received by the reducer';

  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.'
    );
  }

  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('", "')}"`
    );
  }

  var unexpectedKeys = Object.keys(inputState).filter(
    key => reducerKeys.indexOf(key) < 0
  );

  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.`
    );
  }
}

// 對reducer作合法性檢測
// store = Redux.createStore(reducer, initialState) -->
// currentState = initialState
// currentState = currentReducer(currentState, action);
//
// 從調用關係,調用時機來看, store.getState() 的初始值(currentState)
// 爲 currentReducer(initialState, { type: ActionTypes.INIT })
//
// 1. 在初始化階段,reducer 傳入的 state 值是 undefined,此時,須要返回初始state,且初始state不能爲undefined
// 2. 當傳入不認識的 actionType 時, reducer(state, {type}) 返回的不能是undefined
// 3. redux/ 這個 namespace 下的action 不該該作處理,直接返回 currentState 就行 (誰運氣這麼差會去用這種actionType...)
function assertReducerSanity(reducers) {
  Object.keys(reducers).forEach(key => {
    var reducer = reducers[key];
    var initialState = reducer(undefined, { type: ActionTypes.INIT });

    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. The initial state may ` +
        `not be undefined.`
      );
    }

    var 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 probed with a random type. ` +
        `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
        `namespace. They are considered private. Instead, you must return the ` +
        `current state for any unknown actions, unless it is undefined, ` +
        `in which case you must return the initial state, regardless of the ` +
        `action type. The initial state may not be undefined.`
      );
    }
  });
}

/**
 * Turns an object whose values are different reducer functions, into a single
 * reducer function. 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.
 *
 * @param {Object} reducers An object whose values correspond to different
 * reducer functions that need to be combined into one. One handy way to obtain
 * it is to use ES6 `import * as reducers` syntax. The reducers may never return
 * undefined for any action. Instead, they should return their initial state
 * if the state passed to them was undefined, and the current state for any
 * unrecognized action.
 *
 * @returns {Function} A reducer function that invokes every reducer inside the
 * passed object, and builds a state object with the same shape.
 */

export default function combineReducers(reducers) {
  // 返回一個對象, key => value 且value是function(其實就是過濾掉非function)
  var finalReducers = pick(reducers, (val) => typeof val === 'function');
  var sanityError;

  try {
    // 對全部的子reducer 作一些合法性斷言,若是沒有出錯再繼續下面的處理
    // 合法性斷言的內容,見API註釋
    assertReducerSanity(finalReducers);
  } catch (e) {
    sanityError = e;
  }

  // 全部的 key: value,將value置成了undefined,費解...
  // 總而言之, 初始state 就是 相似 {hello: undefined, world: undefined} 的東東
  // TODO 確認這裏的邏輯
  var defaultState = mapValues(finalReducers, () => undefined);

  return function combination(state = defaultState, action) {
    if (sanityError) {
      throw sanityError;
    }

    var hasChanged = false;
    // 這段代碼,簡單的說,就是循環一遍 finalState[key] = fn(reducer, key)
    var finalState = mapValues(finalReducers, (reducer, key) => {
      var previousStateForKey = state[key];
      var nextStateForKey = reducer(previousStateForKey, action);
      if (typeof nextStateForKey === 'undefined') {
        // 其餘一個reducer返回的是undefined,因而掛啦...拋出錯誤
        var errorMessage = getUndefinedStateErrorMessage(key, action);
        throw new Error(errorMessage);
      }
      // 這段代碼有些費解,從redux的設計理念上來說,除了不認識的action type,其餘狀況都應該返回全新的state
      // 也就是說
      // 1. action type 認識,返回新的state,因而這裏 hasChanged 爲 true
      // 2. action type 不認識,返回原來的state,因而這裏 hasChanged 爲 false
      // 3. 無論action type 是否定識, 在原來的state上修改,可是返回的是修改後的state(沒有返回拷貝),那麼,hasChanged仍是爲false
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
      return nextStateForKey;
    });

    // 開發環境中(因而記得在生產環境去掉)
    // 後面再研究這段代碼,畢竟不是主線路...
    if (process.env.NODE_ENV !== 'production') {
      var warningMessage = getUnexpectedStateKeyWarningMessage(state, finalState, action);
      if (warningMessage) {
        console.error(warningMessage);
      }
    }

    return hasChanged ? finalState : state;
  };
}

源碼解析:bindActionCreator.js

別看API註釋一大堆,除去合法性檢查,關鍵代碼其實就只有幾句。先看個簡單例子可能方便理解一些。看完以後可能會以爲,不就是對store.dispatch 的調用進行了便捷處理嘛。。。

var addTodo = function(text){
    return {
        type: 'add_todo',
        text: text
    };
};

var addTodos = function(){
    return {
        type: 'add_todos',
        items: Array.prototype.slice.call(arguments, 0)
    };
};

var reducer = function(state, action){
    switch (action.type) {
        case 'add_todo':
            return state.concat(action.text);
        case 'add_todos':
            return state.concat(action.items);
        default:
            return state;
    }
};


var store = redux.createStore(reducer, []);
// 注意,關鍵代碼在這裏
var actions = redux.bindActionCreators({
    addTodo: addTodo,
    addTodos: addTodos
}, store.dispatch);

console.log('state is: ' + store.getState());

store.dispatch({type: 'add_todo', text: '讀書'});
store.dispatch({type: 'add_todos', items: ['閱讀', '睡覺']});
console.log('state is: ' + store.getState());  // state is: 讀書,閱讀,睡覺

actions.addTodo('看電影');
console.log('state is: ' + store.getState());  // state is: 讀書,閱讀,睡覺,看電影

actions.addTodos(['刷牙', '洗澡']);
console.log('state is: ' + store.getState());  // state is: 讀書,閱讀,睡覺,看電影,刷牙,洗澡

因此,直接看代碼吧,挺簡單的。

import mapValues from '../utils/mapValues';

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...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
 * may be invoked directly. This is just a convenience method, as you can call
 * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
 *
 * 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
 * creator functions. One handy way to obtain it is to use ES6 `import * as`
 * syntax. You may also pass a single function.
 *
 * @param {Function} dispatch The `dispatch` function available on your Redux
 * store.
 *
 * @returns {Function|Object} The object mimicking the original object, but with
 * every action creator wrapped into the `dispatch` call. If you passed a
 * function as `actionCreators`, the return value will also be a single
 * function.
 */
// 假設 actionCreators === {addTodo: addTodo, removeTodo: removeTodo}
// 簡單的來講 bindActionCreators(actionCreators, dispatch)
// 最後返回的是:
// {
//   addTodo: function(text){
//      dispatch( actionCreators.addTodo(text) );
//   },
//   removeTodo: function(text){
//      dispatch( actionCreators.removeTodo(text) );
//   }
// }
//
//  或者說 actionCreators === addTodo (addTodo 爲 actionCreator)
//  最後返回的是
//  function() {
//     dispatch(actionCreators());
//  }
export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch);
  }

  if (typeof actionCreators !== 'object' || actionCreators === null || actionCreators === undefined) {  // eslint-disable-line no-eq-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"?`
    );
  }

  return mapValues(actionCreators, actionCreator =>
    bindActionCreator(actionCreator, dispatch)
  );
}

源碼解析:applyMiddleware.js

中間件應該是redux源碼裏面最繞的一部分,雖然看懂後,有一種「啊~原來不過如此」的感受,但一開始還真是看的暈頭轉向的,API的說明、中間件的編寫、applyMiddleware的源碼實現,都不是那麼好理解。

在繼續源碼解析以前,推薦看下官方文檔對於middleware的說明,連接傳送門:http://camsong.github.io/redu...

雖然文檔死長死長,但硬着頭皮看完,仍是有所收穫的,終於知道 applyMiddleware 的實現這麼繞了。。。

例子:redux-thunk

用redux處理過異步請求的同窗應該用過redux-thunk,咱們來看下他的源碼,奇短無比,別說你的小夥伴了,個人小夥伴都驚呆了。

export default function thunkMiddleware({ dispatch, getState }) {
  return next => action =>
    typeof action === 'function' ?
      action(dispatch, getState) :
      next(action);
}

翻譯成ES5,是這樣子滴,以後你再看其餘中間件的實現,其實都大同小異,下面咱們寫個自定義中間件,基本就能夠看出點門路來。

'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = thunkMiddleware;
function thunkMiddleware(store) {
  var dispatch = store.dispatch;
  var getState = store.getState;

  return function (next) {
    return function (action) {
      return typeof action === 'function' ? action(dispatch, getState) : next(action);
    };
  };
}
module.exports = exports['default'];

自定義中間件:logger

先看logger的實現

function middleware(store){
        return function(next){
            return function(action){
                return next(action);
            }
        }
    }

基本看出中間件聲明的模版來了,就是下面這個樣子。下面結合applyMiddleware的調用,來講明store、next、action 幾個參數。

function logger(store){
        return function(next){
            return function(action){
                console.log('logger: dispatching ' + action.type);
                var result = next(action);
                console.log('logger: next state ' + result);
                return result;
            }
        }
    }

applyMiddleware調用例子

完整的示例代碼見本小節最後面。能夠看到:

  1. applyMiddleware 的調用方式爲 applyMiddleware(...middlewares)(react.createStore)。其實這裏直接先建立 store,而後applyMiddleware(...middlewares)(store) 也很容易實現相同的效果,不過做者是故意這樣設計的,爲了不在同一個store上屢次應用同一個middlerware(參考官方文檔:嘗試 #6: 「單純」地使用 Middleware
  2. 中間件頂層的store參數,並非常規的store,雖然它也有 getState、dispatch 兩個方法

    // 上面的store參數,其實就是這個對象
        // 其中,store 爲內部的store,咱們在外面 storeWithMiddleWare.dipatch的時候,內部實現是轉成 store.dispatch
        // 此外,能夠看到 middlewareAPI.dispatch 方法,是最終封裝後的dispatch(千萬注意,若是在中間件內部 調用 store.dispatch,可能致使死循環 )
        var middlewareAPI = {
          getState: store.getState,
          // 最後面, dispatch 被覆蓋, 變成包裝後的 dispatch 方法
          dispatch: (action) => dispatch(action)
        };
  3. 第二層的next函數,實際上是一個「dispatch」方法。熟悉express的同窗大概能夠猜到它的做用。storeWithMiddleWare.dispatch(action) 的時候,會順序進入各個中間件(按照定義時的順序)。從當前的例子來看,大約以下,其實就是柯里化啦~:
storeWithMiddleWare.dispatch(action) --> logger(store)(next)(action) --> timer(store)(next)(action) --> store.dispatch(action)

完整的示例代碼

function reducer(state, action){
        if(typeof state==='undefined') state = [];

        switch(action.type){
            case 'add_todo':
                return state.concat(action.text);
            default: 
                return state;
        }
    }
    
    function addTodo(text){
        return {
            type: 'add_todo',
            text: text
        };
    }

    // 這裏的 store,並非 redux.createStore(reducer, initialState) 出來的 store
    // 而是 {getState: store.getState, dispatch: function() { store.dispatch(action); }}
    // 
    function logger(store){    
        //     
        return function(next){
            return function(action){
                console.log('logger: dispatching ' + action.type);
                var result = next(action);
                console.log('logger: next state ' + result);
                return result;
            }
        }
    }

    function timer(store){
        return function(next){
            return function(action){
                console.log('timer: dispatching ' + action.type);
                var result = next(action);
                console.log('timer: next state ' + result);
                return result;
            }
        }
    }

    var createStoreWidthMiddleware = redux.applyMiddleware(
        logger, 
        timer
        )(redux.createStore);
    
    var storeWithMiddleWare = createStoreWidthMiddleware(reducer);
    storeWithMiddleWare.subscribe(function(){
        console.log('subscribe: state is : ' + storeWithMiddleWare.getState());
    });
    console.log( storeWithMiddleWare.dispatch(addTodo('reading')) );

源碼解析

再次說下,建議先看下官方文檔對中間件的介紹,否則可能會有點暈。

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
 * asynchronous actions in a concise manner, or logging every action payload.
 *
 * See `redux-thunk` package as an example of the Redux middleware.
 *
 * Because middleware is potentially asynchronous, 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 {...Function} middlewares The middleware chain to be applied.
 * @returns {Function} A store enhancer applying the middleware.
 */
/*
  從調用方法 applyMiddleware(...middlewares)(Redux.createStore) 能夠看出
  next 參數其實是 Redux.createStore. 而 Redux.createStore 的調用方式爲 Redux.createStore(reducer, initialState)
  因此 applyMiddleware(...middlewares)
  1. 參數: Redux.createStore
  2. 返回值:一個function, 跟 Redux.createStore 接受的參數同樣

 */
export default function applyMiddleware(...middlewares) {
  return (next) => (reducer, initialState) => {
    // 內部先建立一個store (至關於直接調用 Redux.createStore(reducer, initialState))
    var store = next(reducer, initialState);
    // 保存最初始的store.dispatch
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      // 最後面, dispatch 被覆蓋, 變成包裝後的 dispatch 方法
      dispatch: (action) => dispatch(action)
    };
    // 返回一個數組
    // 貼個例子在這裏作參考,redux-thunk
    // function thunkMiddleware(store) {
    //  var dispatch = store.dispatch;
    //  var getState = store.getState;
    //
    //  這裏的next其實就是dispatch
    //  return function (next) {
    //    return function (action) {
    //      return typeof action === 'function' ? action(dispatch, getState) : next(action);
    //    };
    //  };
    //}
    /*
      chain 是個數組, 參考上面的 middlleware (redux-thunk),能夠看到,chain的每一個元素爲以下形式的function
      而且, 傳入的 store.getState 爲原始的 store.getState,而 dispatch則是包裝後的 dispatch(不是原始的store.dispatch)
      彷佛是爲了確保, 在每一個middleware裏調用 dispatch(action), 最終都是 用原始的 store.dispatch(action)
      避免 store.dispatch 被覆蓋, 致使middleware 順序調用的過程當中, store.dispatch的值變化 --> store.dispatch 返回的值可能會有不一樣
      違背 redux 的設計理念

      這裏的 next 則爲 原始的 store.dispatch (見下面 compose(...chain)(store.dispatch) )
      function (next) {
        return function (action) {

        }
      }
     */
    chain = middlewares.map(middleware => middleware(middlewareAPI));

    // compose(...chain)(store.dispatch) 返回了一個function
    // 僞代碼以下,
    // function (action) {
    //   middleware(store)(store.dispatch);
    // }
    dispatch = compose(...chain)(store.dispatch);  // 從右到左, middleware1( middleware2( middleware3(dispatch) ) )

    // 因而,最終調用 applyMiddleware(...middlewares)(Redux.createStore)
    // 返回的 store, getState,subscribe 方法都是原始的那個 store.getState, store.subscribe
    // 至於dispatch是封裝過的
    return {
      ...store,
      dispatch
    };
  };
}

相關連接

官方文檔:http://camsong.github.io/redu...
源碼解析github地址:https://github.com/chyingp/re...
源碼解析相關代碼示例:https://github.com/chyingp/re...

相關文章
相關標籤/搜索