通過Redux原始碼學習基礎概念一:簡單例子入門

橙子瓣發表於2016-09-30

    最近公司有個專案使用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幫我們構建資料結構,並且演示如何做多級的資料結構

 

相關文章