理解Javascript的狀態容器Redux
Redux要解決什么問題?
隨著 JavaScript 單頁應用開發日趨復雜, JavaScript 需要管理比任何時候都要多的 state (狀態) 。 這些 state 可能包括服務器響應、緩存數據、本地生成尚未持久化到服務器的數據,也包括 UI 狀態,如激活的路由,被選中的標簽,是否顯示加載動效或者分頁器等等。
管理不斷變化的 state 非常困難。如果一個 model 的變化會引起另一個 model 變化,那么當 view 變化時,就可能引起對應 model 以及另一個 model 的變化,依次地,可能會引起另一個 view 的變化。直至你搞不清楚到底發生了什么。 state 在什么時候,由于什么原因,如何變化已然不受控制。 當系統變得錯綜復雜的時候,想重現問題或者添加新功能就會變得舉步維艱。
Redux 的設計借鑒 Flux 、 Elm 、 Immutable ,它的出現就是為了解決state里的數據問題。
Redux是如何工作的?
我們知道,在React中,數據在組件中是單向流動的。數據從一個方向父組件流向子組件(通過props),由于這個特征,兩個非父子關系的組件(或者稱作兄弟組件)之間的通信并不是那么清楚。
React并不建議直接采用組件到組件的通信方式,盡管它有一些特性可以支持這么做(比如先將子組件的值傳遞給父組件,然后再由父組件在分發給指定的子組件)。這被很多人認為是糟糕的實踐方式,因為這樣的方式容易出錯而且會讓代碼向“拉面”一樣不容易理解。
當然React也沒有直接建議如何去處理這種情形,以下是 React的文檔 中關于這部分的描述:
對于非父子關系的組件,你可以自己建立一個全局的事件系統,Flux的模式也是一種可行的方式。
Redux的出現就讓這個問題的解決變得更加方便了。Redux提供一種存儲整個應用狀態到一個地方的解決方案(可以理解為統一狀態層),稱為“store”,組件將狀態的變化轉發通知(dispatch)給store,而不是直接通知其它的組件。組件內部依賴的state的變化情況可以通過訂閱store來實現。
使用React,所有的組件都從store里面獲取它們依賴的state,同時也需要將state的變化告知store。組件不需要關注在這個store里面其它組件的state的變化情況,Redux讓數據流變得更加簡單。這種思想最初來自Flux,它是一種和React相同的單向數據流的設計模式。
Redux的核心設計理念
Redux有三大原則
- 單一數據源,整個應用的 state 被儲存在一棵 object tree 中,并且這個 object tree 只存在于唯一一個 store 中
- State 是只讀的,惟一改變 state 的方法就是觸發 action,action 是一個用于描述已發生事件的普通對象。
- 使用純函數來執行修改,為了描述 action 如何改變 state tree ,你需要編寫 reducers。
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、Reducer
一個action的例子:
var action = {
type: 'ADD_USER',
user: {name: 'Dan'}
};
// 假設store對象已經通過Redux.createStore()創建
store.dispatch(action);</code></pre>
這段代碼中,通過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是純函數,需要注意:
- 不允許在reducer函數內部進行網絡調用或者數據庫查詢操作
- 不能改變它的參數
這樣做的好處是:每次調用同樣的一個函數,傳入相同的值可以得到相同的結果。對系統的其它部分也不會產生副作用。
第一個Redux store
使用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'}
});</code></pre>
上述代碼運行后發生的事情:
- Store被創建
- reducer確定初始的state的值是空數組
- action被轉發給store
- reducer將newUser添加到state中,并且將它返回,更新store
通過這段代碼可以發現: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。
來自:http://www.cnblogs.com/cpselvis/p/6276789.html