深入redux源碼 (1)

redux 的安裝

npm install redux --save

redux 有五個(gè)js構(gòu)成,純函數(shù)實(shí)現(xiàn)


因?yàn)?redux 是純函數(shù),所以很方便進(jìn)行測(cè)試.在webStorm新建個(gè)node環(huán)境,安裝redux,就可以跑redux的例子.

先看個(gè)最簡(jiǎn)單的使用例子:

/**
 * Created by lijie on 16/8/7.
 */
// 首先定義一個(gè)改變數(shù)據(jù)的plain函數(shù)快集,成為reducer
'use strict';
function count (state, action) {
    var defaultState = {
        year: 2015,
    };
    state = state || defaultState;
    switch (action.type) {
        case 'add':
            return {
                year: state.year + 1
            };
        case 'sub':
            return {
                year: state.year - 1
            }
        default :
            return state;
    }
}

// store的創(chuàng)建
var createStore = require('redux').createStore;
var store = createStore(count);

// store里面的數(shù)據(jù)發(fā)生改變時(shí)炮叶,觸發(fā)的回調(diào)函數(shù)
store.subscribe(function () {
    console.log('the year is: ', store.getState().year);
});

// action: 觸發(fā)state改變的唯一方法(按照redux的設(shè)計(jì)思路)
var action1 = { type: 'add' };
var action2 = { type: 'add' };
var action3 = { type: 'sub' };

// 改變store里面的方法
store.dispatch(action1); // 'the year is: 2016
store.dispatch(action2); // 'the year is: 2017
store.dispatch(action3); // 'the year is: 2016

運(yùn)行結(jié)果如下:


該例子只用到了一個(gè)API: createStore, 先看這個(gè)主干 createStore.js

createStore.js

參數(shù)

function createStore(reducer, initialState, enhancer) {
    ...
}

reducer 示例中的counter函數(shù),作用是根據(jù)action處理state的變化

initialState 初始化的 state 狀態(tài)樹

enhancer 增強(qiáng)函數(shù),需要用applyMiddleware 函數(shù)加入自定義的功能.
例如 常用第三方庫redux-thunkredux-logger

subscribe 函數(shù)

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

listener 表示觀察者函數(shù)
nextListeners: 最新的listener 數(shù)組

該函數(shù)在做的事情: 將之前的listener數(shù)組復(fù)制給nextListener,并將當(dāng)前的注冊(cè)的listener也加入nextListener中

返回的是 清除當(dāng)前觀察者對(duì)象的函數(shù) ,該函數(shù)就將當(dāng)前l(fā)istener從數(shù)組中又拿掉了.以后再通過dispatch發(fā)送action時(shí),該listener將不會(huì)通知該函數(shù)了.

dispatch 函數(shù)

  function dispatch(action) {

    if (!(0, _isPlainObject2["default"])(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 = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    var listeners = currentListeners = nextListeners;
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]();
    }

    return action;
  }

前面一長(zhǎng)串方法都是判斷發(fā)送的Action是否是 對(duì)象,actionType是否是 undefined ,是否已經(jīng)發(fā)送過

最關(guān)鍵部分 currentState = currentReducer(currentState, action);

currentReducer 就是初始化賦值給的reducer函數(shù)
在dispatch 函數(shù)中給到各個(gè)reducer進(jìn)行處理

最后一步 for 循環(huán)通知當(dāng)前所有的觀察者

這樣的話,示例中所運(yùn)行的效果就能解釋通了.

redux 中間件

redux 庫能衍生出豐富的工具集和可拓展的生態(tài)系統(tǒng)

為剛才的示例添加一個(gè) 監(jiān)控?cái)?shù)據(jù)變化的 logger 的中間件

'use strict';

const logger = store => next => action => {
    console.log("pre---"+JSON.stringify(store.getState()));
    const result = next(action);
    console.log("end---"+JSON.stringify(store.getState()));
    return result;
};

function count (state, action) {
    var defaultState = {
        year: 2015,
    };
    state = state || defaultState;
    switch (action.type) {
        case 'add':
            return {
                year: state.year + 1
            };
        case 'sub':
            return {
                year: state.year - 1
            }
        default :
            return state;
    }
}

// store的創(chuàng)建
var createStore = require('redux').createStore;
var applyMiddleware=require('redux').applyMiddleware;

const createStoreWithMiddleware = applyMiddleware(logger)(createStore);
var store=createStoreWithMiddleware(count,undefined);

// store里面的數(shù)據(jù)發(fā)生改變時(shí)唉侄,觸發(fā)的回調(diào)函數(shù)
store.subscribe(function () {
    console.log('the year is: ', store.getState().year);
});
// action: 觸發(fā)state改變的唯一方法(按照redux的設(shè)計(jì)思路)
var action1 = { type: 'add' };

// // 改變store里面的方法
store.dispatch(action1); // 'the year is: 2016

運(yùn)行結(jié)果如下:


實(shí)現(xiàn)該功能 主要用到了applyMiddleware.js的API

applyMiddleware.js

function applyMiddleware() {
  //在這里 找到所有的 第三方中間件 函數(shù)
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function (reducer, initialState, enhancer) {
      //創(chuàng)建store
      var store = createStore(reducer, initialState, enhancer);
      var _dispatch = store.dispatch; //拿到store的dispatch函數(shù)
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch(action) {
          return _dispatch(action);
        }
      };
      //將middlewareAPI 作為第三方中間件 參數(shù),并返回 該所有第三方中間件的函數(shù)數(shù)組
      chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      //從右到左, middleware1( middleware2( middleware3(dispatch) ) )
      _dispatch = _compose2["default"].apply(undefined, chain)(store.dispatch);

      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}

該段代碼的作用: 創(chuàng)建store,將中間件轉(zhuǎn)成數(shù)組,將store的state與dispatch給到中間件,并嵌套執(zhí)行.

在這里面有個(gè)compose.js的api使用.官方解釋作用是 (...args) => f(g(h(...args))).將數(shù)組依次嵌套執(zhí)行.

compose.js

為了更清楚理解compose.js的作用,我們?cè)偬砑右粋€(gè) 第三方中間件.

'use strict';

const logger = store => next => action => {
    console.log("pre---"+JSON.stringify(store.getState()));
    const result = next(action);
    console.log("end---"+JSON.stringify(store.getState()));
    return result;
};
/**
 * 用 { meta: { delay: N } } 來讓 action 延遲 N 毫秒箩溃。
 * 在這個(gè)案例中,讓 `dispatch` 返回一個(gè)取消 timeout 的函數(shù)栋豫。
 */
const timeoutScheduler = store => next => action => {
    if (!action.meta || !action.meta.delay) {
        return next(action)
    }

    let timeoutId = setTimeout(
        () => next(action),
        action.meta.delay
    )

    return function cancel() {
        clearTimeout(timeoutId)
    }
}


function count (state, action) {
    var defaultState = {
        year: 2015,
    };
    state = state || defaultState;
    switch (action.type) {
        case 'add':
            return {
                year: state.year + 1
            };
        case 'sub':
            return {
                year: state.year - 1
            }
        default :
            return state;
    }
}

// store的創(chuàng)建
var createStore = require('redux').createStore;
var applyMiddleware=require('redux').applyMiddleware;

const createStoreWithMiddleware = applyMiddleware(logger,timeoutScheduler)(createStore);
var store=createStoreWithMiddleware(count,undefined);

// store里面的數(shù)據(jù)發(fā)生改變時(shí)晌端,觸發(fā)的回調(diào)函數(shù)
store.subscribe(function () {
    console.log('the year is: ', store.getState().year);
});
// action: 觸發(fā)state改變的唯一方法(按照redux的設(shè)計(jì)思路)
var action1 = { type: 'add', meta: { delay: 2000 }};

// // 改變store里面的方法
store.dispatch(action1); // 'the year is: 2016

添加的中間件 作用是發(fā)送帶timeout的action.運(yùn)行代碼,你會(huì)發(fā)現(xiàn)結(jié)果并沒有變化,只是listener 得到消息的時(shí)間推遲了2000毫秒,但不同的是,logger是實(shí)時(shí)性的.

所以compose.js讓中間件的執(zhí)行順序是 timeoutScheduler( logger(dispatch) )的.

把使用的順序顛倒一下:

const createStoreWithMiddleware = applyMiddleware(timeoutScheduler,logger)(createStore);

將示例的代碼改成這樣. 你會(huì)發(fā)現(xiàn) logger 執(zhí)行順序變慢了2000毫秒.
因?yàn)楝F(xiàn)在的執(zhí)行是: logger( timeoutScheduler(dispatch) )

所以可以得出結(jié)論: compose.js 中執(zhí)行順序是 數(shù)組中排在前面的方法最先執(zhí)行,執(zhí)行完畢后將 dispatch函數(shù) 傳遞給后一個(gè)執(zhí)行. 并且在前面執(zhí)行的中間件將影響其后的中間件.


相關(guān)鏈接

官方gitbook: http://cn.redux.js.org/docs/advanced/Middleware.html

解讀redux工作原理: http://blog.csdn.net/jhqdlove/article/details/51940400

Redux系列x:http://www.cnblogs.com/dh-dh/p/5350352.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市婉陷,隨后出現(xiàn)的幾起案子帚称,更是在濱河造成了極大的恐慌,老刑警劉巖憨攒,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件世杀,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡肝集,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蛛壳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杏瞻,“玉大人所刀,你說我怎么就攤上這事±袒樱” “怎么了浮创?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)砌函。 經(jīng)常有香客問我斩披,道長(zhǎng),這世上最難降的妖魔是什么讹俊? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任垦沉,我火速辦了婚禮,結(jié)果婚禮上仍劈,老公的妹妹穿的比我還像新娘厕倍。我一直安慰自己,他們只是感情好贩疙,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布讹弯。 她就那樣靜靜地躺著,像睡著了一般这溅。 火紅的嫁衣襯著肌膚如雪组民。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天悲靴,我揣著相機(jī)與錄音臭胜,去河邊找鬼。 笑死对竣,一個(gè)胖子當(dāng)著我的面吹牛庇楞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播否纬,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼吕晌,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了临燃?” 一聲冷哼從身側(cè)響起睛驳,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎膜廊,沒想到半個(gè)月后乏沸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爪瓜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年蹬跃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铆铆。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蝶缀,死狀恐怖丹喻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情翁都,我是刑警寧澤碍论,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站柄慰,受9級(jí)特大地震影響鳍悠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坐搔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一藏研、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧薯蝎,春花似錦遥倦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至消略,卻和暖如春堡称,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背艺演。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工却紧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胎撤。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓晓殊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親伤提。 傳聞我的和親對(duì)象是個(gè)殘疾皇子巫俺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 一、什么情況需要redux肿男? 1介汹、用戶的使用方式復(fù)雜 2、不同身份的用戶有不同的使用方式(比如普通用戶和管...
    初晨的筆記閱讀 2,013評(píng)論 0 11
  • http://gaearon.github.io/redux/index.html 舶沛,文檔在 http://rac...
    jacobbubu閱讀 79,900評(píng)論 35 198
  • 上周六參加了一個(gè)公司的面試嘹承,因?yàn)槭呛荛L(zhǎng)時(shí)間以來的第一次面試,發(fā)揮的并不是很好如庭,有兩個(gè)下來一想就明白的問題在當(dāng)時(shí)卻卡...
    夏爾先生閱讀 6,407評(píng)論 0 15
  • 用法 為了對(duì)中間件有一個(gè)整體的認(rèn)識(shí)叹卷,先從用法開始分析。調(diào)用中間件的代碼如下: 源碼 createStore.js#...
    黃子毅閱讀 9,573評(píng)論 4 19
  • 每天早上,在小區(qū)里豪娜,可以見到很多蜘蛛餐胀,它們多靜靜地趴在自己結(jié)的網(wǎng)中央哟楷。 我觀察到的蜘蛛是8條腿瘤载,這和我常觀察的其他...
    自由心空閱讀 523評(píng)論 0 2