隨著 JavaScript 單頁應用開發日趨復雜,JavaScript 需要管理比任何時候都要多的 state (狀態)。 這些 state 可能包括服務器響應、緩存數據、本地生成尚未持久化到服務器的數據,也包括 UI 狀態,如激活的路由,被選中的標簽,是否顯示加載動效或者分頁器等等。
管理不斷變化的 state 非常困難。如果一個 model 的變化會引起另一個 model 變化,那麼當 view 變化時,就可能引起對應 model 以及另一個 model 的變化,依次地,可能會引起另一個 view 的變化。直至你搞不清楚到底發生了什麼。state 在什麼時候,由於什麼原因,如何變化已然不受控制。 當系統變得錯綜復雜的時候,想重現問題或者添加新功能就會變得舉步維艱。
Redux的設計借鑒Flux、 Elm、Immutable,它的出現就是為了解決state裡的數據問題。
我們知道,在React中,數據在組件中是單向流動的。數據從一個方向父組件流向子組件(通過props),由於這個特征,兩個非父子關系的組件(或者稱作兄弟組件)之間的通信並不是那麼清楚。
React並不建議直接采用組件到組件的通信方式,盡管它有一些特性可以支持這麼做(比如先將子組件的值傳遞給父組件,然後再由父組件在分發給指定的子組件)。這被很多人認為是糟糕的實踐方式,因為這樣的方式容易出錯而且會讓代碼向“拉面”一樣不容易理解。
當然React也沒有直接建議如何去處理這種情形,以下是React的文檔中關於這部分的描述:
對於非父子關系的組件,你可以自己建立一個全局的事件系統,Flux的模式也是一種可行的方式。
Redux的出現就讓這個問題的解決變得更加方便了。Redux提供一種存儲整個應用狀態到一個地方的解決方案(可以理解為統一狀態層),稱為“store”,組件將狀態的變化轉發通知(dispatch)給store,而不是直接通知其它的組件。組件內部依賴的state的變化情況可以通過訂閱store來實現。
使用Redux,所有的組件都從store裡面獲取它們依賴的state,同時也需要將state的變化告知store。組件不需要關注在這個store裡面其它組件的state的變化情況,Redux讓數據流變得更加簡單。這種思想最初來自Flux,它是一種和React相同的單向數據流的設計模式。
Redux有三大原則
Redux和Flux的主要區別是Redux裡面是單一的數據源設計,而Flux裡面有多個數據源,單一數據源的設計讓React的組件之間的通信更加方便,同時也便於狀態的統一管理。
根據Redux的文檔,狀態變化的唯一方式是觸發一個action(一個可以描述發生了什麼的對象),這意味著我們不能直接的去修改狀態,取而代之的是我們可以通過轉發action去告訴store我們有改變狀態的意圖。store對象提供了非常少的API,僅僅只有4個方法:
store.dispatch(action)
store.subscribe(listener)
store.getState()
replaceReducer(nextReducer)
通過這幾個API不難發現,store並沒有直接提供setState()方法。
另外,由於它大量使用 pure function 和 plain object 等概念(reducer 和 action creator 是 pure function,state 和 action 是 plain object)這對於項目的穩定性會是非常好的保證。
一個action的例子:
var action = {
type: 'ADD_USER',
user: {name: 'Dan'}
};
// 假設store對象已經通過Redux.createStore()創建
store.dispatch(action);
這段代碼中,通過dispatch() 方法將action傳遞給了store。Action 本質上是 JavaScript 普通對象。我們約定,action 內必須使用一個字符串類型的 type 字段來表示將要執行的動作。多數情況下,type 會被定義成字符串常量。當應用規模越來越大時,建議使用單獨的模塊或文件來存放 action。
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
前面描述過,Redux不允許直接去改變state,必須通過轉發action來告訴store有這個意圖要去改變這個狀態。reducer正是扮演處理轉發過來的action的意圖的函數並且可以改變狀態的角色。一個reducer接受當前的state作為參數,通過返回新的state去改變原有的state:
var someReducer = function(state, action) {
...
return state;
}
由於reducer是純函數,需要注意:
使用Redux.createStore()來創建一個store,並且將所有的reducers作為參數傳遞給它,此處以一個reducer為例子:
var userReducer = function(state=[], action) {
if (action.type === 'ADD_USER') {
var newState = state.concat([action.user]);
return newState;
}
return state;
}
// 創建一個store,並且將reducer作為參數傳遞給它
var store = Redux.createStore(userReducer);
// 將action傳遞給store,告訴store我們有改變狀態的意向
store.dispatch({
type: 'ADD_USER',
user: {name: 'cpselvis'}
});
上述代碼運行後發生的事情:
通過這段代碼可以發現:reducer函數實際上被執行了2次,一次時store創建的時候,一次是action被轉發之後。另外需要注意:Redux希望reducer函數總是返回一個新的狀態。這時的結果:
store.getState(); // => [{name: 'cpselvis'}]
到這裡,component -> action -> store -> reducer -> state 的單向數據流的問題就講完了。概括的說就是:React組件裡面獲取了數據之後(比如ajax請求),然後創建一個action通知store我��這個想改變state的意圖,然後reducers(一個action可能對應多個reducer,可以理解為action為訂閱的主題,可能有多個訂閱者)來處理這個意圖並且返回新的state,接下來store會收集到所有的reducer的state,最後更新state。