理解Javascript的狀態容器Redux

dengweihao 7年前發布 | 15K 次閱讀 Redux JavaScript開發 JavaScript

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

 

 本文由用戶 dengweihao 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!