最近公司有個項目使用react+redux來作前端部分的實現,正好有機會學習一下redux,也和小夥伴們分享一下學習的經驗。前端
首先聲明一下,這篇文章講的是Redux的基本概念和實現,不包括react-redux。react
源碼地址:https://github.com/lyc-chengzi/reactProjectjquery
首先說一下我理解的Redux:git
它只是一個管理數據的一個工具,幫助咱們建立app中惟一的一個數據結構的樹,而且按照約定的方法來管理這顆樹,讓咱們的數據的更改變爲可預測的。github
任何一個普通的框架,或者如angular, jquery等均可以依賴於這個數據結構,來作本身的事情。json
react-redux:redux
這個庫則是幫助咱們將react組件和redux建立的數據樹串聯起來,讓普通的react組件根據state來從新渲染。數據結構
之後可能也會有angular-redux, jquery-redux等等庫,幫助咱們實現其餘框架的ui渲染。閉包
好了,下面進入正題:app
Redux的運行原理
1. 先定義好咱們的reducer -> 2. 組裝reducer -> 3. 調用redux.createStore建立一個store -> 4. store調用dispatch方法 ->5. 觸發你寫的reducer -> 6. 返回新的state
舉一個簡單的例子,咱們的app就是一個計數器,實現加和減的功能,一個最簡單的數據結構:{counter: 0};下面開始按照上面的步驟實現
1. 先定義一個咱們的reducer,其實就是一個回調函數
1 function counter(state = 0, action){ 2 switch (action.type){ 3 case 'counter_add': 4 return ++state; 5 break; 6 7 default: 8 return state; 9 } 10 }
reducer固定會接收兩個參數,state和action。
reducer的做用就是接受一箇舊的state,而後內部加工處理後,返回一個新的state給redux,這就是reducer的職能:擴展或修改原有state並返回!
第一個參數state就是redux告訴咱們的更改前的數據,咱們以此爲基礎作一些操做。具體是那些操做,就經過第二個參數action告訴咱們。
如上面的代碼,經過action.type,咱們處理了counter_add action,即數字加1操做,咱們把state+1;其餘未知操做咱們直接返回原有state。
這樣一個最簡單的reducer就建立完了,是否是很簡單? 就是一個普通的回調函數
2. 組裝reducer
1 var app = function(state = {}, action){ 2 return {counter: counter(state.counter, action)}; 3 };
這一步的目的是返回一個根reducer,由於默認state爲undefined,因此咱們給state一個默認值{}。根reducer返回一個json對象,key爲名稱,value爲具體的實現reducer
3. 建立store
let store = redux.createStore(app);
console.log(store.getState());
簡單的2行代碼,經過咱們定義的根reducer,redux建立一個store對象返回給咱們。
咱們只能經過dispatch方法來改變整個app的state,調用getState方法查看初始化後的數據結構
4. 調用dispatch,來實現計數器增長
1 store.dispatch({type: 'counter_add'}); 2 console.log(store.getState());
dispatch方法只接受一個action參數。
action爲一個json對象:必須包含type屬性,用來標識是哪個action,也能夠有其餘屬性做爲附加值傳遞到reducer
這裏咱們傳遞了'counter_add'告訴redux。
這個action會從你的根reducer一直傳遞下去,到末級reducer。只要咱們定義的reducer處理這個action,就會更新state。
而後咱們打印最新的state,以下
若是咱們要更新state,只能經過調用store.dispatch方法,傳遞action參數。而後redux會調用咱們的reducer來處理這個action,最後return 最新的state。
下面咱們經過源碼來看一下關鍵的兩個函數是如何運行的。
1. createStore
1 function createStore(reducer, preloadedState, enhancer) { 2 var currentReducer = reducer; 3 var currentState = preloadedState; 4 var currentListeners = []; 5 var nextListeners = currentListeners; 6 var isDispatching = false; 7 8 dispatch({ type: ActionTypes.INIT }); 9 10 return _ref2 = { 11 dispatch: dispatch, 12 subscribe: subscribe, 13 getState: getState, 14 replaceReducer: replaceReducer 15 }, _ref2[_symbolObservable2['default']] = observable, _ref2; 16 }
上面是createStore的關鍵代碼。
使用了閉包的技巧,隱藏了幾個關鍵變量:
currentReducer=>咱們傳入的根reducer
currentState => 當前默認state,咱們默認爲一個空json對象{}
nextListeners和currentListeners用來保存監聽函數,當咱們調用dispatch方法時會觸發
isDispatching => 當前調度狀態,只有當前調度狀態是false時纔會執行dispatch方法
初始化完幾個關鍵內部變量後,執行了一次默認的dispatch方法,action.type爲reduxInit
最後返回了一個包裝對象,包含了對外公開的方法。咱們只能經過這幾個方法來操做內部的變量。
(雖然能夠var state= store.getState();獲取state以後直接修改,但千萬不要這麼作,否則redux也沒有意義了。我的認爲若是getState()返回一個clone的currentState會更好)
2.咱們來看一下dispatch都幹了些什麼
1 function dispatch(action) { 2 if (!(0, _isPlainObject2['default'])(action)) { 3 throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.'); 4 } 5 6 if (typeof action.type === 'undefined') { 7 throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?'); 8 } 9 10 if (isDispatching) { 11 throw new Error('Reducers may not dispatch actions.'); 12 } 13 14 try { 15 isDispatching = true; 16 currentState = currentReducer(currentState, action); 17 } finally { 18 isDispatching = false; 19 } 20 21 var listeners = currentListeners = nextListeners; 22 for (var i = 0; i < listeners.length; i++) { 23 listeners[i](); 24 } 25 26 return action; 27 }
很是簡單,只是調用了你根reducer函數,而後將內部保存的當前state,和action傳了過去,剩下的都是你的reducer乾的事情了。
因此createStore默認調用了一次dispatch,action.type爲init,咱們的reducer沒有對應的處理方法,直接將默認的state返回了回去。
如今也就明白了爲何咱們的reducer爲何要在default的時候返回變化前的state。
因此總結一下redux,就是dispatch的過程,(由於createStore也是dispatch,不過是在內部調用的),每一次dispatch都會調用一次咱們的根reducer,而後從新構建一遍數據,
而後把新的數據保存起來。
到此咱們就把一個最簡單的redux例子學完了。下一篇將會介紹另外一種組裝reducer的方法:經過調用
redux.combineReducers
方法讓redux幫咱們構建數據結構,而且演示如何作多級的數據結構