書籍完整目錄前端
前面咱們介紹了 flux 架構以及其開源實現 redux,在這一節中,咱們將完整的介紹 redux:react
redux 介紹git
redux 是什麼github
redux 概念shell
redux 三原則npm
redux Stores編程
redux Actionredux
redux Reducerssegmentfault
redux 數據流動前端框架
Redux is a predictable state container for JavaScript apps
譯:Redux 是爲 Javascript 應用而生的可預估的狀態容器
定義有些抽象,簡單來說 redux 能夠理解爲基於 flux 和其餘一些思想(Elm,函數式編程)發展出來的前端應用架構庫,做爲一個前端數據狀態容器體現,並能夠在 React 和其餘任何前端框架中使用。
store: 同 flux,應用數據的存儲中心
action: 同 flux ,應用數據的改變的描述
reducer: 決定應用數據新狀態的函數,接收應用以前的狀態和一個 action 返回數據的新狀態。
middleware: redux 提供中間件的方式,完成一些 flux 流程的自定義控制,同時造成其插件體系
單一的 store
區別於 flux 模式中能夠有多個 state,整個應用的數據以樹狀結構存放在一個 state 對象中。
console.log(store.getState()) /* Prints { visibilityFilter: 'SHOW_ALL', todos: [ { text: 'Consider using Redux', completed: true, }, { text: 'Keep all state in a single tree', completed: false } ] } */
state 只讀
state 包含的應用數據不能隨意修改,修改數據的惟一方式是 dispatch action,action 描述要修改的信息(這和 flux 架構上是一致的,不過在設計上更加嚴格,見後面的 reducer 設計)。
store.dispatch({ type: 'COMPLETE_TODO', index: 1 }) store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_COMPLETED' })
數據的改變由純函數生成
在 redux 中,應用數據的改變路徑爲:
store.dispatch(action)
newState = reducer(previousState, action)
reducer 爲純函數
純函數是函數是編程的思想,只要參數相同,函數返回的結果永遠是一致的,而且不會對外部有任何影響(不會改變參數對象的數據)。也就是說 reducer 每次必須返回一個新狀態,新狀態由舊狀態和 action 數據決定。
安裝 redux 核心和 react-redux 集成進 react
$ npm install --save redux $ npm install --save react-redux
在 redux 中 store 做爲一個單例,存放了應用的全部數據,對外提供了以下的 API:
獲取數據 store.getState()
經過觸發 action 來更新數據 store.dispatch(action)
pubsub 模式提供消息訂閱和取消 store.subscribe(listener)
redux 提供 createStore
方法來建立一個 store
/** * [createStore description] * @param {[type]} reducer [reducer 函數] * @param {[type]} initialState [應用初始化狀態] * @return {[type]} [description] */ function createStore(reducer, initialState) { // .... }
建立一個 store :
var redux = require('redux'); var todoAppReducer = require('./reducers'); var initalState = { todos: [] }; var store = redux.createStore(todoAppReducer, initialState);
redux 修改應用數據的惟一方式爲 store.dispatch(action)
eg:
store.dispatch({ type: 'ADD_TODO', title: 'new todo' })
爲了讓用戶監聽應用數據改變,redux 在 store 集成了 pubsub 模式
訂閱
// 每次應用數據改變,輸出最新的應用數據 store.subscribe(function(){ console.log(store.getState()) }) // 若是新增了 todo 會觸發上面的訂閱函數 store.dispatch({ type: 'ADD_TODO', title: 'new todo' })
取消
subscribe 返回的函數即爲取消函數
var unsubscribe = store.subscribe(function(){ console.log(store.getState()) }) // .... unsubscribe();
全部數據都存放在一個 store 中,以 todoApp 爲例子,state 的數據結構可能爲
{ visibilityFilter: 'SHOW_ALL', todos: [ { text: 'Consider using Redux', completed: true, }, { text: 'Keep all state in a single tree', completed: false } ] }
當應用變大的時候,數據結構可能沒有這麼簡單,這時候須要找到一個較好的結構來設計應用數據,下面是兩個 redux 在設計 state 上的 tip:
業務數據和 UI 狀態數據分離,儘可能避免 UI 狀態數據放在 store 中,即使放在 store 中也好和業務數據分離。
避免嵌套,在一個複雜的場景,數據對象可能很深,出現多層,那在設計的時候能夠經過 id 的方式來引用,能夠參考 normalizr 的簡化方式
咱們已經知道 action 就是數據修改的描述信息,不過在實際使用過程當中須要理解下面的這些規範:
action 描述數據結構
action 類型常量
action creator
redux 對 action 對象的數據結構作了簡單規範,每一個對象必須包含一個字段 type,應用經過 type 來識別 action 類型,其餘字段不作任何限制。
eg:
{ type: "ADD_TODO", text: 'Build my first Redux app' } { type: "REMVOE_TODO", index: 1 } { type: "TOGGLE_TODO", id: 'a1s2d1' }
爲了項目的規範,一般把 action type 定義爲名稱常量,放在一個單獨的文件中管理,這在大型項目中是一個很好的習慣。
eg:
var ADD_TODO = "ADD_TODO" { type: ADD_TODO, text: 'Build my first Redux app' }
在 flux 模式小節已經介紹過,爲了規範化 action 經過 action creator 來建立
function addTodo(text) { return { type: ADD_TODO, text: text } }
原來的 dispatch 的使用改成了
// 舊的方式 store.dispatch({ type: ADD_TODO, text: text }) // 新的方式 store.dispatch(addTodo(text))
reducer 應該是最爲陌生的概念,理解 reducer 是理解 redux 的關鍵,牢記 reducer 決定應用數據的改變
/** * [reducer description] * @param {[type]} previewsState [以前狀態] * @param {[type]} action [redux action] * @return {[type]} newState [新狀態] */ function reducer(previewsState, action) { var newState; switch(action.type) { case .. case .. default: newState = previewsState } return newState; }
首先 reducer 是一個純函數,接收到以前的應用狀態和 action 並返回一個新狀態,爲了每次返回一個新狀態,能夠經過 Object.assign() 方法返回一個新的對象,也可使用 Immutable.js (後面的章節會講解 Immutable.js)。
function todoApp(state, action) { state = initialState switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) default: return state } }
redux 會在初始的時候調用一次 reducer (這時參數 previewsState 爲 undefined), 能夠借用這個機會返回應用初始狀態數據。
eg:
// reducers/todoApp.js var initialState = {todos: ...}; function todoApp(state, action) { if (typeof state === 'undefined') { return initialState } // 返回默認值 return state } module.exports = todoApp;
總結須要注意的點:
純函數特性,不能修改 previewsState,不能調用異步 API,不管何時,傳入相同的參數都能當即返回相同的結果(不能調用 Math.random, Data.now 這些函數,由於會致使不一樣的結果)
默認返回 previewsState (在 action 不會獲得處理的狀況)
處理 state 爲 undefined 的狀況
一個 todo 應用可能有不少操做,在真實場景上面的 todoApp reducer 可能膨脹爲以下的結構:
function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) case ADD_TODO: return Object.assign({}, state, { todos: [ ...state.todos, { text: action.text, completed: false } ] }) case TOGGLE_TODO: return Object.assign({}, state, { todos: state.todos.map((todo, index) => { if(index === action.index) { return Object.assign({}, todo, { completed: !todo.completed }) } return todo }) }) default: return state } }
這時候能夠對 todoApp reducer 作拆分,將它拆分爲多個不一樣的 reducer,todoApp reducer 的做用只是組合其餘 reducer 。
拆分後能夠以下:
function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [ ...state, { text: action.text, completed: false } ] case TOGGLE_TODO: return state.map((todo, index) => { if (index === action.index) { return Object.assign({}, todo, { completed: !todo.completed }) } return todo }) default: return state } } function visibilityFilter(state = SHOW_ALL, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter default: return state } } function todoApp(state = {}, action) { return { visibilityFilter: visibilityFilter(state.visibilityFilter, action), todos: todos(state.todos, action) } } module.exports = todoApp
todoApp 再也不負責整個應用狀態的修改,而是將責任分配給了其餘的 reducer, 每一個子 reducer 相互獨立,負責各自的責任,這個時候應用數據的改變不是靠一個 reducer 改變的,而是由多個 reducer 組合起來,這是 redux 應用的基礎,叫作 reducer 組合。
能夠發現 reducer 的組合和狀態數據的結構是相同的,均可以理解爲樹狀結構。 state 根對象有一個根 reducer (createState 傳入的 reducer)這裏是 todoApp , state 對象下面的第一級數據對應不用的 reducer,visibilityFilter 和 todos , 理解起來這也是一個典型的 分而治之策略。
對於 reducer 的組合,redux 提供了 combineReducers()
方法來簡化,上面的 todoApp 能夠簡化爲:
var todoApp = redux.combineReducers({ visibilityFilter: visibilityFilter, todos: todos })
這樣的寫法和上面的寫法做用徹底相同
redux 繼承 flux 最核心的地方是 數據的單向流動,
上面已經詳細介紹了 redux 的各個概念,下面將這些概念結合起來,看看數據怎麼在 redux 中流動的。
第一步:調用 store.dispatch(action)
能夠在任何地方觸發 dispatch,例如 UI 交互,API 調用
第二步: Redux store 調用 rootReducer
redux 收到 action 事後,調用根 reducer 並返回最新的狀態數據。(根 reducer 內部組合其餘 reducer 返回部分的最新狀態)
第三步:接收新狀態並 publish 給訂閱者
當 rootReducer 返回最新的狀態後,通知訂閱函數 store.subscribe(listener)
。在 React 中,能夠訂閱狀態更新,在訂閱函數中獲取最新的狀態事後,修改根組件的數據:
component.setState(newState)