最近公司有個專案使用react+redux來做前端部分的實現,正好有機會學習一下redux,也和小夥伴們分享一下學習的經驗。
首先宣告一下,這篇文章講的是Redux的基本概念和實現,不包括react-redux。
原始碼地址:https://github.com/lyc-chengzi/reactProject
首先說一下我理解的Redux:
它只是一個管理資料的一個工具,幫助我們建立app中唯一的一個資料結構的樹,並且按照約定的方法來管理這顆樹,讓我們的資料的更改變為可預測的。
任何一個普通的框架,或者如angular, jquery等都可以依賴於這個資料結構,來做自己的事情。
react-redux:
這個庫則是幫助我們將react元件和redux建立的資料樹串聯起來,讓普通的react元件根據state來重新渲染。
以後可能也會有angular-redux, jquery-redux等等庫,幫助我們實現其他框架的ui渲染。
好了,下面進入正題:
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幫我們構建資料結構,並且演示如何做多級的資料結構