理解Javascript的狀態容器Redux

Redux要解決什麼問題?

隨着 JavaScript 單頁應用開發日趨複雜,JavaScript 須要管理比任什麼時候候都要多的 state (狀態)。 這些 state 可能包括服務器響應、緩存數據、本地生成還沒有持久化到服務器的數據,也包括 UI 狀態,如激活的路由,被選中的標籤,是否顯示加載動效或者分頁器等等。
管理不斷變化的 state 很是困難。若是一個 model 的變化會引發另外一個 model 變化,那麼當 view 變化時,就可能引發對應 model 以及另外一個 model 的變化,依次地,可能會引發另外一個 view 的變化。直至你搞不清楚到底發生了什麼。state 在何時,因爲什麼緣由,如何變化已然不受控制。 當系統變得錯綜複雜的時候,想重現問題或者添加新功能就會變得舉步維艱。
Redux的設計借鑑FluxElmImmutable,它的出現就是爲了解決state裏的數據問題。Redux和Flux的主要區別是Redux裏面是單一的數據源設計,而Flux(或者Reflux)裏面有多個數據源。javascript

Redux是如何工做的?

咱們知道,在React中,數據在組件中是單向流動的。數據從一個方向父組件流向子組件(經過props),因爲這個特徵,兩個非父子關係的組件(或者稱做兄弟組件)之間的通訊並非那麼清楚。
React並不建議直接採用組件到組件的通訊方式,儘管它有一些特性能夠支持這麼作(好比先將子組件的值傳遞給父組件,而後再由父組件在分發給指定的子組件)。這被不少人認爲是糟糕的實踐方式,由於這樣的方式容易出錯並且會讓代碼向「拉麪」同樣不容易理解。
固然React也沒有直接建議如何去處理這種情形,如下是React的文檔中關於這部分的描述:html

對於非父子關係的組件,你能夠本身創建一個全局的事件系統,Flux的模式也是一種可行的方式。java

Redux的出現就讓這個問題的解決變得更加方便了。Redux提供一種存儲整個應用狀態到一個地方的解決方案(能夠理解爲統一狀態層),稱爲「store」,組件將狀態的變化轉發通知(dispatch)給store,而不是直接通知其它的組件。組件內部依賴的state的變化狀況能夠經過訂閱store來實現。
使用Redux,全部的組件都從store裏面獲取它們依賴的state,同時也須要將state的變化告知store。組件不須要關注在這個store裏面其它組件的state的變化狀況,Redux讓數據流變得更加簡單。這種思想最初來自Flux,它是一種和React相同的單向數據流的設計模式。
react

Redux的核心設計理念

Redux有三大原則git

  • 單一數據源,整個應用的 state 被儲存在一棵 object tree 中,而且這個 object tree 只存在於惟一一個 store 中
  • State 是隻讀的,唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象。
  • 使用純函數來執行修改,爲了描述 action 如何改變 state tree ,你須要編寫 reducers。

單一數據源的設計讓React的組件之間的通訊更加方便,同時也便於狀態的統一管理。
根據Redux的文檔,狀態變化的惟一方式是觸發一個action(一個能夠描述發生了什麼的對象),這意味着咱們不能直接的去修改狀態,取而代之的是咱們能夠經過轉發action去告訴store咱們有改變狀態的意圖。store對象提供了很是少的API,僅僅只有4個方法:github

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)這對於項目的穩定性會是很是好的保證。ajax

理解Action、Reducer

一個action的例子:數據庫

var action = {
  type: 'ADD_USER',
  user: {name: 'Dan'}
};

// 假設store對象已經經過Redux.createStore()建立
store.dispatch(action);

這段代碼中,經過dispatch() 方法將action傳遞給了store。Action 本質上是 JavaScript 普通對象。咱們約定,action 內必須使用一個字符串類型的 type 字段來表示將要執行的動做。多數狀況下,type 會被定義成字符串常量。當應用規模愈來愈大時,建議使用單獨的模塊或文件來存放 action。redux

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'}
});

上述代碼運行後發生的事情:

  • Store被建立
  • reducer肯定初始的state的值是空數組
  • action被轉發給store
  • reducer將newUser添加到state中,而且將它返回,更新store

經過這段代碼能夠發現:reducer函數實際上被執行了2次,一次是store建立的時候,一次是action被轉發以後。另外須要注意:Redux但願reducer函數老是返回一個新的狀態。這時的結果:

store.getState();  // => [{name: 'cpselvis'}]

經過這個例子,能夠總結出Redux的工做流程如圖:

到這裏,component -> action -> store -> reducer -> state 的單向數據流的問題就講完了。歸納的說就是:React組件裏面獲取了數據以後(好比ajax請求),而後建立一個action通知store我有這個想改變state的意圖,而後reducers(一個action可能對應多個reducer,能夠理解爲action爲訂閱的主題,可能有多個訂閱者)來處理這個意圖而且返回新的state,接下來store會收集到全部的reducer的state,最後更新state。

相關文章
相關標籤/搜索