Redux源码全篇浅读
本文是關于 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這幾個函數(shù)。
1. compose
先來看看compose函數(shù),這個比較簡單。
export default function compose() {//funcs保存著所有參數(shù)函數(shù)的數(shù)組for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {funcs[_key] = arguments[_key];}//省略一些邏輯判斷……//reduce方法使用指定的函數(shù)將數(shù)組元素進行組合,生成單個值return funcs.reduce(function (a, b) {return function () {return a(b.apply(undefined, arguments));};}); } 復制代碼其中主要就是對 reduce 的理解了。reduce()方法使用指定的函數(shù)將數(shù)組元素進行組合(從左到右),生成單個值。具體可以查看Array.prototype.reduce() | MDN
compose的功能為從右到左,組合參數(shù)(函數(shù))。傳遞給compose方法的參數(shù)是函數(shù)類型的,
compose(f, g, h) 相當于 (...args) => f(g(h(...args)))
2. applyMiddleware 與 中間件鏈
故名思義這個函數(shù)就是應用中間件,一般使用方式為:
//thunk代表react-thunk中間件 const store = createStore(rootReducer, initialState, applyMiddleware(thunk)); 復制代碼2.1 中間件及react-thunk
在看applyMiddleware函數(shù)前,先來簡單看一下中間件,redux中間件在發(fā)起action到達reducer之間擴展了一些功能。一般用于記錄日志(reudx-logger)和增加異步調用(redux-thunk , redux-saga)等。這些中間件一般按照一定的格式書寫,比如react-thunk2.2.0的源碼:
//react-thunk2.2.0的代碼,很簡單 只有十幾行…… //外層函數(shù)可以傳入多余的參數(shù) function createThunkMiddleware(extraArgument) {//獲取dispatch getState(是由applyMiddleware傳入)return function (_ref) {var dispatch = _ref.dispatch,getState = _ref.getState;//1. 如果這個中間件是調用鏈最內環(huán)的,next指原store.dispatch //2. 其他next一般指上一個中間件的返回值 action => {}//對于這個next的賦值不清楚的話可以結合之后的applyMiddleware函數(shù)return function (next) {return function (action) {//1. 原action只是個普通的對象,thunk使action可以傳入函數(shù)類型,并傳入了dispatch, getState, extraArgument//2. 如果action是個異步函數(shù),thunk會調用該函數(shù)if (typeof action === 'function') {return action(dispatch, getState, extraArgument);}//3. 函數(shù)調用結束后,獲取必要的數(shù)據(jù)再次觸發(fā)dispatch由此實現(xiàn)異步效果。return next(action);};};}; } 復制代碼可以看到react-thunk中間件是一個多層的高階函數(shù),格式大致為:
({dispatch,getState}) => next => action => {return next(action)} 復制代碼中間件的格式都要遵循這樣相似的格式,這是由applyMiddleware函數(shù)決定的。接下來看下applyMiddleware的源碼:
2.2 applyMiddleware源碼
//applyMiddleware源碼 export default function applyMiddleware() {//middlewares保存?zhèn)鬟M來的中間件for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {middlewares[_key] = arguments[_key];}//createStore是創(chuàng)建createStore的函數(shù),會在下文解讀,這里先不管return function (createStore) {return function (reducer, preloadedState, enhancer) {//創(chuàng)建store 并獲取了其dispatch方法var store = createStore(reducer, preloadedState, enhancer);var _dispatch = store.dispatch;var chain = [];//用于傳遞給中間件第一層函數(shù)的參數(shù),上文在thunk中有看到var middlewareAPI = {getState: store.getState,dispatch: function dispatch(action) {return _dispatch(action);}};//middlewares保存的是中間件,chain對應保存的就是中間件第二層函數(shù)組成的數(shù)組//形象點就是上文中間件格式去掉第一層:next => action => {}chain = middlewares.map(function (middleware) {return middleware(middlewareAPI);});//1. componse的功能在上文說到,假設chain為[F1,F2,F3],compose之后變成了F1(F2(F3))//2. 與上文thunk中說到中間件格式對應,F3是中間件鏈的最內環(huán) 所以F3的next參數(shù)為store.dispatch//3. F2的next參數(shù)就是F3返回的 action => {}//4. 同樣的F1的next參數(shù)就是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一路經(jīng)過上一個中間件封裝后的變種dispatch。
2.3 中間件鏈的執(zhí)行順序
光看代碼可能對于中間件鏈的執(zhí)行順序的理解還是優(yōu)點蒙,這里來做個實踐加深一下理解。
一、 先按上文說的中間件格式({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參數(shù)是logger的action層函數(shù)(參數(shù)為action那層函數(shù))
- m2的next參數(shù)是m3的action層函數(shù)
- m1的next參數(shù)是thunk的action層函數(shù)
三、 執(zhí)行一下 dispatch(action) :
這時候分兩種情況:
1、當action類型為對象時,在chrome控制臺看到如下信息:
發(fā)起 dispatch(action) action類型為對象middleware1 action層 開始 middleware2 action層 開始 middleware3 action層 開始 action SOME_OBJ_ACTION redux-logger.js:1 middleware3 action層 結束 middleware2 action層 結束 middleware1 action層 結束 復制代碼粗粗一看好像順序不對啊,不該先執(zhí)行middleware3的邏輯嘛?其實內層(位置靠后的中間件)只是返回了一個function,并沒有執(zhí)行其中的邏輯,不斷由外層的中間件包裹形成了一個‘洋蔥模型’。由外向內穿心而過,再由內向外完成流程。
這樣子就很明確了,中間件的action層執(zhí)行順序為先加入中間件鏈的先執(zhí)行!,更準確的說中間件中先執(zhí)行從外層向內層中 next(action)之前的邏輯,然后執(zhí)行從內層向外層中 next(action)之后的邏輯。
2、當action類型為函數(shù)時,在chrome控制臺看到如下信息:
發(fā)起 dispatch(action) action類型為函數(shù)middleware1 action層 開始 middleware1 action層 結束 復制代碼可以看到只執(zhí)行了 middleware1 和 thunk 兩個中間件!這是因為thunk沒有執(zhí)行 next(action) 中斷了中間件鏈! 當中間件沒有執(zhí)行next(action)時會導致中間件鏈中斷,這是因為dispatch沒有傳遞下去,所以中間件還可以搗亂咯~
3. bindActionCreators
bindActionCreators的功能是為action creaters包裝上dispatch,使其調用action時自動dispatch對應的action。
在bindActionCreators.js中只有兩個函數(shù) bindActionCreator 和 bindActionCreators。
//這個函數(shù)的主要作用就是返回一個函數(shù),當我們調用返回的這個函數(shù)的時候,就會自動的dispatch對應的action function bindActionCreator(actionCreator, dispatch) {return function () {return dispatch(actionCreator.apply(undefined, arguments));}; }export default function bindActionCreators(actionCreators, dispatch) {//省略一些邏輯判斷// 獲取所有action creater函數(shù)的名字var keys = Object.keys(actionCreators);// 保存dispatch和action creater函數(shù)進行綁定之后的集合var boundActionCreators = {};//為每個actionCreators 包裝上 dispatchfor (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只是一些純函數(shù),它接收之前的state和action,并返回新的state。當應用變大,我們可以拆分多個小的reducers,分別獨立的操作state tree的不同部分。而combineReducers就是將多個不同的reducer合并成一個最終的reducer,用于賦值給createStore函數(shù)。
//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函數(shù)就相當于結合所有reducers之后新的reducerreturn function combination() {var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};var action = arguments[1];......var hasChanged = false;var nextState = {};//這里遍歷了所有之前自定義的reducers,并記錄下是否state有改變,并記錄下改變的statefor (var _i = 0; _i < finalReducerKeys.length; _i++) {var _key = finalReducerKeys[_i];var reducer = finalReducers[_key];var previousStateForKey = state[_key];//遍歷所有的reducer,若previousStateForKey匹配到則返回新的state//若匹配不到就在reducer中dufault中返回原statevar nextStateForKey = reducer(previousStateForKey, action);......nextState[_key] = nextStateForKey;//這里有點意思,并沒有因為找到不同的state就直接返回//這意味著,多個子reducers可以對同個action返回自己的state//并且返回的state是依據(jù)靠后的reducer的返回值決定的hasChanged = hasChanged || nextStateForKey !== previousStateForKey;}return hasChanged ? nextState : state;}; } 復制代碼5. createStore
現(xiàn)在來看下最重要的createStore函數(shù):
/* * redux有且僅有一個store,createStore函數(shù)就是用于創(chuàng)建一個store用來存放所有的state。 * @param {Function} reducer * @param {any} [preloadedState] 初始化state * @param {Function} [enhancer] store的增強器,有applyMiddleware、時間旅行(time travel)、持久化(persistence)等。 */ 復制代碼上面是createStore的參數(shù),其中enhancer指的是store的增強器(對store API進行改造)。
- applyMiddleware在前面我們已經(jīng)見過了,applyMiddleware是對store的dispatch函數(shù)的修改。
- 時間旅行則是指redux可以回到任意以前的狀態(tài)。 - 這是因為Redux使用簡單的對象來表示state狀態(tài),并使用純函數(shù)計算下一個狀態(tài)。這意味著如果給定一個特定state狀態(tài)和一個特定action操作,那么下一個狀態(tài)將始終完全相同。這種特性可以讓我們將所有修改過的狀態(tài)保存在一個狀態(tài)歷史對象中,通過指定恢復到狀態(tài)歷史對象中從前的狀態(tài),來完成時間旅行。
- 比較容易可以想到的應用就是一些撤銷/重做操作。
 
- 持久化持久化這個概念肯定大家都熟悉,也有人做出了實現(xiàn):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; //當前的statevar currentListeners = []; //訂閱函數(shù)var nextListeners = currentListeners;//訂閱函數(shù)備份,//用于解決listeners數(shù)組執(zhí)行過程(for循環(huán))中,取消訂閱listener產生的listeners數(shù)組index錯誤。//這樣保證在某個dispatch后,會保證在這個dispatch之前的所有事件監(jiān)聽器全部執(zhí)行var isDispatching = false; //dispatch方法同步標志function ensureCanMutateNextListeners() {if (nextListeners === currentListeners) {nextListeners = currentListeners.slice();}}/*** @returns {any} 當前state*/function getState() {return currentState;}/** 將一個訂閱函數(shù)放到 listeners 隊列里,當 dispatch 的時候,逐一調用 listeners 中的回調方法。 * @param {Function} listener函數(shù)* @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.');}//調用reducertry {isDispatching = true;//更新state樹currentState = currentReducer(currentState, action);} finally {isDispatching = false;}//調用nextListeners中的監(jiān)聽方法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;//觸發(fā)生成新的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; } 復制代碼以上就是我個人的簡單見解,如果有什么錯誤之處,敬請指導討論
參考資料:
- Redux 入門教程(一):基本用法 | 阮一峰
- Redux 中文文檔
總結
以上是生活随笔為你收集整理的Redux源码全篇浅读的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: @Autowired注解和静态方法 No
- 下一篇: 楚留香捏脸数据女神(汉典楚字的基本解释)
