Redux原始碼全篇淺讀

66CCFF發表於2019-02-25

本文是關於 redux(3.7.2)原始碼的一些淺讀

redux原始碼目錄中 ,可以看到以下檔案目錄:

|-- utils/
    |-- warning.js              //列印error
|-- 1. applyMiddleware.js          // 
|-- 2. bindActionCreators.js       //
|-- 3. combineReducers.js          //
|-- 4. compose.js                  //
|-- 5. createStore.js              //
|-- index.js                    //入口檔案
複製程式碼

與檔案對應的,主要也是介紹 createStore compose combineReducers bindActionCreators applyMiddleware這幾個函式。

1. compose

先來看看compose函式,這個比較簡單。

export default function compose() {
  //funcs儲存著所有引數函式的陣列
  for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
    funcs[_key] = arguments[_key];
  }

  //省略一些邏輯判斷……

  //reduce方法使用指定的函式將陣列元素進行組合,生成單個值
  return funcs.reduce(function (a, b) {
    return function () {
      return a(b.apply(undefined, arguments));
    };
  });
}
複製程式碼

其中主要就是對 reduce 的理解了。reduce()方法使用指定的函式將陣列元素進行組合(從左到右),生成單個值。具體可以檢視Array.prototype.reduce() | MDN

compose的功能為從右到左,組合引數(函式)。傳遞給compose方法的引數是函式型別的,

compose(f, g, h) 相當於 (…args) => f(g(h(…args)))

2. applyMiddleware 與 中介軟體鏈

故名思義這個函式就是應用中介軟體,一般使用方式為:

//thunk代表react-thunk中介軟體
const store = createStore(rootReducer, initialState, applyMiddleware(thunk));
複製程式碼

2.1 中介軟體及react-thunk

在看applyMiddleware函式前,先來簡單看一下中介軟體,redux中介軟體在發起action到達reducer之間擴充套件了一些功能。一般用於記錄日誌(reudx-logger)和增加非同步呼叫(redux-thunk , redux-saga)等。這些中介軟體一般按照一定的格式書寫,比如react-thunk2.2.0的原始碼:

//react-thunk2.2.0的程式碼,很簡單 只有十幾行……
//外層函式可以傳入多餘的引數
function createThunkMiddleware(extraArgument) {
  //獲取dispatch getState(是由applyMiddleware傳入)
  return function (_ref) {
    var dispatch = _ref.dispatch,
        getState = _ref.getState;
    //1. 如果這個中介軟體是呼叫鏈最內環的,next指原store.dispatch    
    //2. 其他next一般指上一個中介軟體的返回值 action => {}
    //對於這個next的賦值不清楚的話可以結合之後的applyMiddleware函式
    return function (next) {
      return function (action) {
        //1. 原action只是個普通的物件,thunk使action可以傳入函式型別,並傳入了dispatch, getState, extraArgument
        //2. 如果action是個非同步函式,thunk會呼叫該函式
        if (typeof action === `function`) {
          return action(dispatch, getState, extraArgument);
        }
        //3. 函式呼叫結束後,獲取必要的資料再次觸發dispatch由此實現非同步效果。
        return next(action);
      };
    };
  };
}
複製程式碼

可以看到react-thunk中介軟體是一個多層的高階函式,格式大致為:

({dispatch,getState}) => next => action => {return next(action)}
複製程式碼

中介軟體的格式都要遵循這樣相似的格式,這是由applyMiddleware函式決定的。接下來看下applyMiddleware的原始碼:

2.2 applyMiddleware原始碼

//applyMiddleware原始碼
export default function applyMiddleware() {
  //middlewares儲存傳進來的中介軟體
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  //createStore是建立createStore的函式,會在下文解讀,這裡先不管
  return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
      //建立store 並獲取了其dispatch方法
      var store = createStore(reducer, preloadedState, enhancer);
      var _dispatch = store.dispatch;
      var chain = [];

	  //用於傳遞給中介軟體第一層函式的引數,上文在thunk中有看到
      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch(action) {
          return _dispatch(action);
        }
      };
      //middlewares儲存的是中介軟體,chain對應儲存的就是中介軟體第二層函式組成的陣列
      //形象點就是上文中介軟體格式去掉第一層:next => action => {}
      chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      //1. componse的功能在上文說到,假設chain為[F1,F2,F3],compose之後變成了F1(F2(F3))
      //2. 與上文thunk中說到中介軟體格式對應,F3是中介軟體鏈的最內環 所以F3的next引數為store.dispatch
      //3. F2的next引數就是F3返回的 action => {}
      //4. 同樣的F1的next引數就是F2返回的 action => {}      
      //
      //_dispatch就相當於F1(F2(F3(store.dispatch)))
      //這樣多箇中介軟體就組合到了一起,形成了中介軟體鏈
      _dispatch = compose.apply(undefined, chain)(store.dispatch);

	  //新的dispatch會覆蓋原dispatch,之後呼叫dispatch同時會呼叫中介軟體鏈
      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}
複製程式碼

_dispatch = compose.apply(undefined, chain)(store.dispatch);組合後,中介軟體的next就是store.dispatch一路經過上一個中介軟體封裝後的變種dispatch。

2.3 中介軟體鏈的執行順序

光看程式碼可能對於中介軟體鏈的執行順序的理解還是優點蒙,這裡來做個實踐加深一下理解。

一、 先按上文說的中介軟體格式({dispatch,getState}) => next => action => {return next(action)} 寫三個中介軟體:


function middleware1({dispatch,getState}) {
    return function(next) {
        console.log(`middleware1 next層 next:`,next);
        return function(action) {
            console.log(`middleware1 action層 開始`)
            next(action)
            console.log(`middleware1 action層 結束`)
        }
    }
}
function middleware2({dispatch,getState}) {
    return function(next) {
        console.log(`middleware2 next層 next:`,next);
        return function(action) {
            console.log(`middleware2 action層 開始`)
            next(action)
            console.log(`middleware2 action層 結束`)
        }
    }
}
function middleware3({dispatch,getState}) {
    return function(next) {
        console.log(`middleware3 next層 next:`,next);
        return function(action) {
            console.log(`middleware3 action層 開始`)
            next(action)
            console.log(`middleware3 action層 結束`)
        }
    }
}
複製程式碼

二、 將它們和redux-thunk 和 redux-logger一起加入中介軟體鏈:

const middlewares = [
    middleware1,
    thunkMiddleWare,
    middleware2,
    middleware3,
    loggerMiddleWare,//redux-logger需要放在最後面
];

...

const store = createStore(rootReducer, initialState, applyMiddleware(...middlewares));
複製程式碼

執行你的程式碼後在chrome控制檯看到如下資訊:

middleware3 next層 next: ƒ (l){if("function"==typeof …
 
middleware2 next層 next: ƒ (action){
console.log(`middleware3 action層 開始`);
next(action);
console.log(`middleware3 action層 結束`);
}
 
middleware1 next層 next: ƒ (action) {
        if (typeof action === `function`) {
          return action(dispatch, getState, extraArgument);
        }

        return next(action);
      }
複製程式碼

這也驗證了上文對 _dispatch = compose.apply(undefined, chain)(store.dispatch);的理解。

這裡的中介軟體鏈是由 [m1,thunk,m2,m3,logger]組成,在compose之後變成了 m1(thunk(m2(m3(logger)))),
所以

  • m3的next引數是logger的action層函式(引數為action那層函式)
  • m2的next引數是m3的action層函式
  • m1的next引數是thunk的action層函式

三、 執行一下 dispatch(action) :

這時候分兩種情況:

1、當action型別為物件時,在chrome控制檯看到如下資訊:

發起 dispatch(action) action型別為物件

middleware1 action層 開始
middleware2 action層 開始
middleware3 action層 開始
action SOME_OBJ_ACTION       redux-logger.js:1
middleware3 action層 結束
middleware2 action層 結束
middleware1 action層 結束
複製程式碼

粗粗一看好像順序不對啊,不該先執行middleware3的邏輯嘛?其實內層(位置靠後的中介軟體)只是返回了一個function,並沒有執行其中的邏輯,不斷由外層的中介軟體包裹形成了一個‘洋蔥模型’。由外向內穿心而過,再由內向外完成流程。

這樣子就很明確了,中介軟體的action層執行順序為先加入中介軟體鏈的先執行!,更準確的說中介軟體中先執行從外層向內層中 next(action)之前的邏輯,然後執行從內層向外層中 next(action)之後的邏輯。

2、當action型別為函式時,在chrome控制檯看到如下資訊:

發起  dispatch(action) action型別為函式

middleware1 action層 開始
middleware1 action層 結束
複製程式碼

可以看到只執行了 middleware1 和 thunk 兩個中介軟體!這是因為thunk沒有執行 next(action) 中斷了中介軟體鏈! 當中介軟體沒有執行next(action)時會導致中介軟體鏈中斷,這是因為dispatch沒有傳遞下去,所以中介軟體還可以搗亂咯~

3. bindActionCreators

bindActionCreators的功能是為action creaters包裝上dispatch,使其呼叫action時自動dispatch對應的action。

在bindActionCreators.js中只有兩個函式 bindActionCreatorbindActionCreators

//這個函式的主要作用就是返回一個函式,當我們呼叫返回的這個函式的時候,就會自動的dispatch對應的action
function bindActionCreator(actionCreator, dispatch) {
  return function () {
    return dispatch(actionCreator.apply(undefined, arguments));
  };
}

export default function bindActionCreators(actionCreators, dispatch) {

	//省略一些邏輯判斷

  // 獲取所有action creater函式的名字
  var keys = Object.keys(actionCreators);
  // 儲存dispatch和action creater函式進行繫結之後的集合
  var boundActionCreators = {};
  //為每個actionCreators 包裝上 dispatch
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    var actionCreator = actionCreators[key];
    if (typeof actionCreator === `function`) {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
    }
  }
  return boundActionCreators;
}
複製程式碼

4. combineReducers

Reducer只是一些純函式,它接收之前的state和action,並返回新的state。當應用變大,我們可以拆分多個小的reducers,分別獨立的操作state tree的不同部分。而combineReducers就是將多個不同的reducer合併成一個最終的reducer,用於賦值給createStore函式。

//combineReducers的程式碼比較簡單,在省略一些錯誤判斷的程式碼後:
export default function combineReducers(reducers) {  
  var reducerKeys = Object.keys(reducers);
  var finalReducers = {};
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i];

  ......

    if (typeof reducers[key] === `function`) {
      finalReducers[key] = reducers[key];
    }
  }
  var finalReducerKeys = Object.keys(finalReducers);

  //從上面到這裡都是為了儲存finalReducerKeys 和 finalReducers
  ......
  
  //返回的combination函式就相當於結合所有reducers之後新的reducer
  return function combination() {
    var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    var action = arguments[1];

	......

    var hasChanged = false;
    var nextState = {};
    //這裡遍歷了所有之前自定義的reducers,並記錄下是否state有改變,並記錄下改變的state
    for (var _i = 0; _i < finalReducerKeys.length; _i++) {
      var _key = finalReducerKeys[_i];
      var reducer = finalReducers[_key];
      var previousStateForKey = state[_key];
      //遍歷所有的reducer,若previousStateForKey匹配到則返回新的state
      //若匹配不到就在reducer中dufault中返回原state
      var nextStateForKey = reducer(previousStateForKey, action);
      ......
      nextState[_key] = nextStateForKey;
      //這裡有點意思,並沒有因為找到不同的state就直接返回
      //這意味著,多個子reducers可以對同個action返回自己的state
      //並且返回的state是依據靠後的reducer的返回值決定的
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    }
    return hasChanged ? nextState : state;
  };
}
複製程式碼

5. createStore

現在來看下最重要的createStore函式:

/*
* redux有且僅有一個store,createStore函式就是用於建立一個store用來存放所有的state。
* @param {Function}  reducer
* @param {any} [preloadedState] 初始化state
* @param {Function} [enhancer] store的增強器,有applyMiddleware、時間旅行(time travel)、持久化(persistence)等。
*/
複製程式碼

上面是createStore的引數,其中enhancer指的是store的增強器(對store API進行改造)。

  • applyMiddleware在前面我們已經見過了,applyMiddleware是對store的dispatch函式的修改。
  • 時間旅行則是指redux可以回到任意以前的狀態。
    • 這是因為Redux使用簡單的物件來表示state狀態,並使用純函式計算下一個狀態。這意味著如果給定一個特定state狀態和一個特定action操作,那麼下一個狀態將始終完全相同。這種特性可以讓我們將所有修改過的狀態儲存在一個狀態歷史物件中,通過指定恢復到狀態歷史物件中從前的狀態,來完成時間旅行。
    • 比較容易可以想到的應用就是一些撤銷/重做操作。
  • 持久化持久化這個概念肯定大家都熟悉,也有人做出了實現:redux-persist

介紹完enhancer,來接著看程式碼邏輯:

export default function createStore(reducer, preloadedState, enhancer) {
  var _ref2;

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

    return enhancer(createStore)(reducer, preloadedState);
  }

  ......

  var currentReducer = reducer;      	//
  var currentState = preloadedState; 	//當前的state
  var currentListeners = [];           //訂閱函式
  var nextListeners = currentListeners;//訂閱函式備份,
  //用於解決listeners陣列執行過程(for迴圈)中,取消訂閱listener產生的listeners陣列index錯誤。
  //這樣保證在某個dispatch後,會保證在這個dispatch之前的所有事件監聽器全部執行
  
  var isDispatching = false; 			  //dispatch方法同步標誌

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice();
    }
  }

  /**
   * @returns {any} 當前state
   */
  function getState() {
    return currentState;
  }

  /*
  * 將一個訂閱函式放到 listeners 佇列裡,當 dispatch 的時候,逐一呼叫 listeners 中的回撥方法。    
  * @param {Function} listener函式
  * @return {Function} 解除繫結的方法
  */
  function subscribe(listener) {
    if (typeof listener !== `function`) {
      throw new Error(`Expected listener to be a function.`);
    }

    var isSubscribed = true;

    ensureCanMutateNextListeners();
    nextListeners.push(listener);

	//解除訂閱的方法,
    return function unsubscribe() {
      if (!isSubscribed) {
        return;
      }

      isSubscribed = false;

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

  /**
   * dispatch方法,呼叫reducer
   *
   * @param {Object} action 
   *
   * @returns {Object} 一般會返回action,
   * 如果使用了中介軟體,可能返回promise 或者function之類的()
   */
  function dispatch(action) {
    ......

	//dispatch是同步的,用isDispatching標誌來判斷
    if (isDispatching) {
      throw new Error(`Reducers may not dispatch actions.`);
    }
    //呼叫reducer
    try {
      isDispatching = true;
      //更新state樹
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

	//呼叫nextListeners中的監聽方法
    var listeners = currentListeners = nextListeners;
    for (var i = 0; i < listeners.length; i++) {
      var listener = listeners[i];
      listener();
    }

    return action;
  }

  /**
   * 替換reducer
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== `function`) {
      throw new Error(`Expected the nextReducer to be a function.`);
    }

    currentReducer = nextReducer;
    //觸發生成新的state樹
    dispatch({ type: ActionTypes.INIT });
  }

  /**
   *略
   */
  function observable() {
    ......
  }

  // 生成初始state樹
  dispatch({ type: ActionTypes.INIT });

  return _ref2 = {
    dispatch: dispatch,
    subscribe: subscribe,
    getState: getState,
    replaceReducer: replaceReducer
  }, _ref2[$$observable] = observable, _ref2;
}
複製程式碼

以上就是我個人的簡單見解,如果有什麼錯誤之處,敬請指導討論


參考資料:

相關文章