http://gaearon.github.io/redux/index.html ,文檔在 http://rackt.github.io/redux/index.html 。本文不是官方文檔的翻譯。你能夠在閱讀官方文檔以前和以後閱讀本文,以加深其中的重點概念。javascript
根據該項目源碼的習慣,示例都是基於 ES2015 的語法來寫的。php
Redux 是應用狀態管理服務。雖然自己受到了 Flux 很深的影響,可是其核心概念卻很是簡單,就是 Map/Reduce 中的 Reduce。css
咱們看一下 Javascript 中 Array.prototype.reduce
的用法:html
const initState = ''; const actions = ['a', 'b', 'c']; const newState = actions.reduce( ( (prevState, action) => prevState + action ), initState );
從 Redux 的角度來看,應用程序的狀態相似於上面函數中的 initState
和 newState
。給定 initState
以後,隨着 action
的值不斷傳入給計算函數,獲得新的 newState
。前端
這個計算函數被稱之爲 Reducer
,就是上例中的 (prevState, action) => prevState + action
。java
Redux 認爲,一個應用程序中,全部應用模塊之間須要共享訪問的數據,都應該放在 State
對象中。這個應用模塊多是指 React Components,也多是你本身訪問 AJAX API 的代理模塊,具體是什麼並無必定的限制。State
以 「樹形」 的方式保存應用程序的不一樣部分的數據。這些數據可能來自於網絡調用、本地數據庫查詢、甚至包括當前某個 UI 組件的臨時執行狀態(只要是須要被不一樣模塊訪問)、甚至當前窗口大小等。react
Redux 沒有規定用什麼方式來保存 State
,多是 Javascript 對象,或者是 Immutable.js 的數據結構。可是有一點,你最好確保 State 中每一個節點都是 Immutable 的,這樣將確保 State 的消費者在判斷數據是否變化時,只要簡單地進行引用比較便可,例如:git
newState.todos === prevState.todos
從而避免 Deep Equal 的遍歷過程。github
爲了確保這一點,在你的 Reducer
中更新 State
成員須要這樣作:數據庫
`let myStuff = [ {name: 'henrik'} ] myStuff = [...mystuff, {name: 'js lovin fool']`
myStuff
是一個全新的對象。
若是更新的是 Object ,則:
let counters = { faves: 0, forward: 20, } // this creates a brand new copy overwriting just that key counters = {...counters, faves: counters.faves + 1}
而不是:
counters.faves = counters.faves + 1}
要避免對 Object 的 in-place editing。數組也是同樣:
let todos = [ { id: 1, text: 'have lunch'} ] todos = [...todos, { id: 2, text: 'buy a cup of coffee'} ]
而不是:
let todos = [ { id: 1, text: 'have lunch'} ] todos.push({ id: 2, text: 'buy a cup of coffee'});
遵循這樣的方式,無需 Immutable.js 你也可讓本身的應用程序狀態是 Immutable 的。
在 Redux 中,State
只能經過 action
來變動。Reducer
就是根據 action
的語義來完成 State
變動的函數。Reducer
的執行是同步的。在給定 initState
以及一系列的 actions
,不管在什麼時間,重複執行多少次 Reducer
,都應該獲得相同的 newState
。這使得你的應用程序的狀態是能夠被 Log 以及 Replay 的。這種肯定性,下降了前端開發所面臨的複雜狀態的亂入問題。肯定的狀態、再加上 Hot-Reloaidng 和相應的 Dev-Tool,使得前端應用的可控性大大加強了。
Redux (Flux) 都建議在保存 State
數據的時候,應該儘量地遵循範式,避免嵌套數據結構。若是出現了嵌套的對象,那麼儘可能經過 ID 來引用。
假設遠程服務返回的數據是這樣的:
[{ id: 1, title: 'Some Article', author: { id: 1, name: 'Dan' } }, { id: 2, title: 'Other Article', author: { id: 1, name: 'Dan' } }]
那麼,轉換成如下形式會更有效率:
{ result: [1, 2], entities: { articles: { 1: { id: 1, title: 'Some Article', author: 1 }, 2: { id: 2, title: 'Other Article', author: 1 } }, users: { 1: { id: 1, name: 'Dan' } } } }
範式化的存儲讓你的數據的一致性更好,上例中,若是更新了users[1].name
,那麼在顯示 articles
的 component 中,做者姓名也被更新了。
其實傳統關係數據庫的設計原則就是如此,只不過隨着對數據分佈能力和水平擴展性的要求(放棄了必定程度的數據一致性),服務端數據的冗餘愈來愈多。可是回到客戶端,因爲須要保存的數據總量不大(每每就是用戶最近訪問數據的緩存),也沒有分佈式的要求,所以範式化的數據存儲就更有優點了。除了能夠收穫一致性,還能夠減小存儲空間(存儲空間在客戶端更加寶貴)。
除此以外,範式化的存儲也利於後面講到的 Reducer
局部化,便於將大的 Reducer
分割爲一系列小的 Reducers
。
因爲服務器端返回的 JSON 數據(如今常見的方式)每每是冗餘而非範式的,所以,可能須要一些工具來幫助你轉換,例如:https://github.com/gaearon/normalizr , 雖然不少時候本身控制會更有效一些。
下面咱們以熟悉 todoApp
來看一下 Reducer
的工做方式:
function todoAppReducer(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 }] }); default: return state; } }
這個例子演示了 Reducers
是如何根據傳入的 action.type
分別更新不一樣的 State
字段。
若是當應用程序中存在不少 action.type
的時候,經過一個 Reducer
和巨型 switch
顯然會產生難以維護的代碼。此時,比較好的方法就是經過組合小的 Reducer
來產生大的 Reducer
,而每一個小 Reducer
只負責處理 State
的一部分字段。以下例:
import { combineReducers } from 'redux'; const todoAppReducer = combineReducers({ visibilityFilter: visibilityFilterReducer todos: todosReducer });
visibilityFilterReducer
和 todosReducer
是兩個小 Reducers
,其中一個以下:
function visibilityFilterReducer(state = SHOW_ALL, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter; default: return state; } }
visibilityFilterReducer
僅僅負責處理 State.visibilityFilter
字段的狀態(經過 action.type
爲 SET_VISIBILITY_FILTER
的 action 來改變)。Reducers
劃分是經過向 combineReducers
傳遞以下形式的參數實現的:
{ field1: reducerForField1, field2: reducerForField2 }
filed1
和 filed2
表示 State
中的字段,reducerForField1
和 reducerForField2
是對應的 Reducers
,每一個 Reducers
將僅僅得到 State.field1
或者 state.field2
的值,而看不到 State
下的其餘字段的內容。響應的返回結果也會被合併到對應的 State
字段中。每一個 Reducer
若是遇到本身不能處理的 action
,那麼必須原樣返回傳入的 state
,或者該 Reducer
設定的初始狀態(若是傳入的 state
是 undefined
)。
使用 combineReducers
的前提是,每個被組合的 Reducer
僅僅和 State
的一部分數據相關,例如:todos Reducer
只消費 State.todos
數據,也只產生 State.todos
數據。這個基本的原則和上面提到的「State 結構設計」範式相結合,能夠知足咱們大部分需求。
不過,有時咱們就是須要在一個 Reducer
之中訪問另一個 Reducer
負責的 state
,這須要咱們建立更上一層的 Reducer
(Root Reducer) 來控制這個過程,例如:
function a(state, action) { } function b(state, action, a) { } // depends on a's state function something(state = {}, action) { let a = a(state.a, action); let b = b(state.b, action, a); // note: b depends on a for computation return { a, b }; }
在這個例子中,咱們有兩個 Reducers
, a
和 b
,其中,b
在計算本身的 state
的還須要依賴 a
的計算結果。所以,咱們就不能依靠 combineReducers
來完成這種需求,而是須要本身寫 Root Reducer 了。reduce-reducers 也能夠幫咱們完成相似的任務:
var reducers = reduceReducers( combineReducers({ router: routerReducer, customers, stats, dates, filters, ui }), // cross-cutting concerns because here `state` is the whole state tree (state, action) => { switch (action.type) { case 'SOME_ACTION': const customers = state.customers; const filters = state.filters; // ... do stuff } } );
上面的例子裏,在 combineReducers
的基礎上,若是某些 action
須要觸發跨 Reducers
的狀態改變,則能夠用上面的寫法。reduce-reducers 組合(每一個參數就是一個 Reducer
)的每個 Reducer
均可以獲取整個 State
,因此請不要濫用(請參見相關討論:https://github.com/reactjs/redux/issues/749 ),在大部分狀況下,若是嚴格遵循數據範式,經過計算的方法得到跨越 Reducers
的狀態是推薦的方法(http://redux.js.org/docs/recipes/ComputingDerivedData.html )。
一個 Reducer
能夠處理多種 action.type
,而 一種 action.type
也可能被多個 Reducers
處理,這是多對多的關係。如下 Helper 函數能夠簡化 Reducer
的建立過程:
function createReducer(initialState, handlers) { return function reducer(state = initialState, action) { if (handlers.hasOwnProperty(action.type)) { return handlers[action.type](state, action); } else { return state; } } } export const todosReducer = createReducer([], { [ActionTypes.ADD_TODO](state, action) { let text = action.text.trim(); return [...state, text]; } }
在 Redux 中,Store
對象就是用來維護應用程序狀態的對象。構造 Store
對象,僅須要提供一個 Reducer 函數便可。如前所述,這個 Reducer
函數是負責解釋 Action
對象的語義,從而改變其內部狀態(也就是應用程序的狀態)。
所以 Store 對象有兩個主要方法,一個次要方法:
store.getState()
: 獲取最近的內部狀態對象。store.dispatch(action)
: 將一個 action
對象發送給 reducer
。一個次要方法爲:const unsure = store.subscribe(listener)
,用來訂閱狀態的變化。在 React + Redux 的程序中,並不推薦使用 store.subscribe
。可是若是你的應用程序是基於 Observable 模式的,則能夠用這個方法來進行適配;例如,你能夠經過這個方法將 Redux 和你的 FRP (Functional Reactive Programming) 應用結合。
下面這個例子演示了 Store
是如何創建的:
import { combineReducers, createStore } from 'redux'; import * as reducers from './reducers'; const todoAppReducer = combineReducers(reducers); const store = createStore(todoAppReducer); // Line 5 store.dispatch({type: 'ADD_TODO', text: 'Build Redux app'});
咱們也能夠在 createStore
的時候爲 Store
指定一個初始狀態,例如替換第 5 行爲:
const store = createStore(reducers, window.STATE_FROM_SERVER);
這個例子中,初始狀態來自於保存在瀏覽器 window
對象的 STATE_FROM_SERVER
屬性。這個屬性可不是瀏覽器內置屬性,是咱們的 Web Server 在返回的頁面文件中之內聯 JavaScript 方式嵌入的。這是一種 Universal(Isomorphic) Application 的實現方式。Client 無需發起第一個 AJAX API 請求,就能夠直接從當前頁面中直接得到初始狀態。
在 Redux 中,改變 State
只能經過 action
。而且,每個 action
都必須是 Javascript Plain Object,例如:
{ type: 'ADD_TODO', text: 'Build Redux app' }
Redux 要求 action
是能夠被序列化的,使這得應用程序的狀態保存、回放、Undo 之類的功能能夠被實現。所以,action
中不能包含諸如函數調用這樣的不可序列化字段。
action
的格式是有建議規範的,能夠包含如下字段:
{ type: 'ADD_TODO', payload: { text: 'Do something.' }, `meta: {}` }
若是 action
用來表示出錯的狀況,則可能爲:
{ type: 'ADD_TODO', payload: new Error(), error: true }
type
是必需要有的屬性,其餘都是可選的。完整建議請參考 Flux Standard Action(FSA) 定義。已經有很多第三方模塊是基於 FSA 的約定來開發了。
事實上,建立 action
對象不多用這種每次直接聲明對象的方式,更多地是經過一個建立函數。這個函數被稱爲Action Creator
,例如:
function addTodo(text) { return { type: ADD_TODO, text }; }
Action Creator 看起來很簡單,可是若是結合上 Middleware 就能夠變得很是靈活。
若是你用過 Express,那麼就會熟悉它的 Middleware 系統。在 HTTP Request 到 Response 處理過程當中,一系列的 Express Middlewares 起着不一樣的做用,有的 Middleware 負責記錄 Log,有的負責轉換內部異常爲特定的 HTTP Status 返回值,有的負責將 Query String 轉變到 request
對象的特定屬性。
Redux Middleware 的設計動機確實是來自於 Express 。其主要機制爲,創建一個 store.dispatch
的鏈條,每一個 middleware 是鏈條中的一個環節,傳入的 action 對象逐步處理,直到最後吐出來是 Javascript Plain Object。先來看一個例子:
import { createStore, combineReducers, applyMiddleware } from 'redux'; // applyMiddleware takes createStore() and returns// a function with a compatible API. let createStoreWithMiddleware = applyMiddleware( logger, crashReporter )(createStore); // Use it like you would use createStore()let todoApp = combineReducers(reducers); let store = createStoreWithMiddleware(todoApp);
這個例子中,logger
和 crashReporter
這兩個 Middlewares 分別完成記錄 action
日誌和記錄 action
處理異常的功能。
logger
的代碼以下:
`// Logs all actions and states after they are dispatched. const logger = store => next => action => { console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; };`
logger
是一個 currying (這是函數式編程的一個基本概念,相比 Flux,Redux 大量使用了函數式編程的範式)以後的函數。next
則是下一個 Middleware 返回的 dispatch
函數(後面會有分析)。對於一個 Middleware 來講,有了 store
對象,就能夠經過 store.getState()
來獲取最近的應用狀態以供決策,有了 next
,則能夠控制傳遞的流程。
ES6 的 Fat Arrow Function 語法(logger = store => next => action =>
)讓本來 function
返回 function
的語法變得更簡潔(I love ☕️script!)。
工業化的 logger
實現能夠參見:https://github.com/fcomb/redux-logger 和 https://github.com/fcomb/redux-diff-logger 。同一個做者寫了兩個,後面這個支持 State
的差別顯示。
Middleware 還能夠用來對傳入的 action
進行轉換,下面這個例子裏,傳入的 action
是一個 Promise(顯然不符合 action
必須是 Javascript Plain Object 的要求),所以須要進行轉換:
/** * Lets you dispatch promises in addition to actions. * If the promise is resolved, its result will be dispatched as an action. * The promise is returned from `dispatch` so the caller may handle rejection. */ const vanillaPromise = store => next => action => { if (typeof action.then !== 'function') { return next(action); } // the action is a promise, we should resolve it first return Promise.resolve(action).then(store.dispatch); };
這個例子中,若是傳入的 action
是一個 Promise
(即包含 .then 函數,這只是一個粗略的判斷),那麼就執行這個 Promise
,當 Promise
執行成功後,將結果直接傳遞給 store.dispatch
(這個例子中咱們短路了 Middlewares 鏈中的後續環節)。固然,咱們要確保 Promise
的執行結果返回的是 Javascript Plain Object。
這種用法可能並不是經常使用,可是從這個例子咱們能夠體會到,咱們能夠定義本身 action
的語義,而後經過相應的 middleware 進行解析,產生特定的執行邏輯以生成最終的 action
對象。這個執行過程多是同步的,也多是異步的。
從這個例子你可能也會發現,若是們也裝載了 logger
Middleware,那麼 logger
能夠知道 Promise action
進入了 dispatch
函數鏈條,可是卻沒有機會知道最終 Promise
執行成功/失敗後發生的事情,由於不管 Promise
執行成功與否,都會直接調用最原始的 store.dispatch
,沒有走 Middlewares 建立的 dispatch
函數鏈條。
對 Promise 的完整支持請參見:https://github.com/acdlite/redux-promise 。
下面這個例子略微複雜一些,演示瞭如何延遲執行一個 action
的 dispatch
。
/** * Schedules actions with { meta: { delay: N } } to be delayed by N milliseconds. * Makes `dispatch` return a function to cancel the interval in this case. */ const timeoutScheduler = store => next => action => { if (!action.meta || !action.meta.delay) { return next(action); } let intervalId = setTimeout( () => next(action), action.meta.delay ); return function cancel() { clearInterval(intervalId); }; };
這個例子中,timeoutScheduler
Middleware 若是發現傳入的 action
參數帶有 meta.delay
字段,那麼就認爲這個 action
須要延時發送。當聲明的延遲時間(meta.delay
)到了,action
對象纔會被送往下一個 Middleware 的 dispatch
方法。
下面這個 Middleware 很是簡單,可是卻提供了很是靈活的用法。
若是不瞭解 Thunk 的概念,能夠先閱讀 http://www.ruanyifeng.com/blog/2015/05/thunk.html 。
thunk
Middleware 的實現很是簡單:
const thunk = store => next => action => typeof action === 'function' ? action(store.dispatch, store.getState) : next(action);
下面的例子裝載了 thunk
,且 dispatch
了一個 Thunk 函數做爲 action
。
const createStoreWithMiddleware = applyMiddleware( logger, thunk timeoutScheduler )(createStore); const store = createStoreWithMiddleware(combineReducers(reducers)); function addFave(tweetId) { return (dispatch, getState) => { if (getState.tweets[tweetId] && getState.tweets[tweetId].faved) return; dispatch({type: IS_LOADING}); // Yay, that could be sync or async dispatching remote.addFave(tweetId).then( (res) => { dispatch({type: ADD_FAVE_SUCCEED}) }, (err) => { dispatch({type: ADD_FAVE_FAILED, err: err}) }, }; } store.dispatch(addFave());
這個例子演示了 「收藏」 一條微博的相關的 action
對象的產生過程。addFave
做爲 Action Creator
,返回的不是 Javascript Plain Object,而是一個接收 dispatch
和 getState
做爲參數的 Thunk 函數。
當 thunk
Middleware 發現傳入的 action
是這樣的 Thunk 函數時,就會爲該函數配齊 dispatch
和 getState
參數,讓 Thunk 函數得以執行,不然,就調用 next(action)
讓後續 Middleware 得到 dispatch
的機會。
在 Thunk 函數中,首先會判斷當前應用的 state
中的微博是否已經被 fave 過了,若是沒有,纔會調用遠程方法。
若是須要調用遠程方法的話,那麼首先發出 IS_LOADING
action
,告訴 關心這個狀態的reducer
一個遠程調用啓動了。從而讓 reducer
能夠更新對應的 state
屬性。這樣關心此狀態的 UI Component
則能夠據此更新界面提示信息。
遠程方法若是調用成功,就會 dispatch
表明成功的 action
對象({type: ADD_FAVE_SUCCEED}
),不然,產生的就是表明失敗的 action
對象({type: ADD_FAVE_FAILED, err: err}
),天然會有關心這兩個 action
的 reducer
來據此更新狀態。不管如何,reducer
最後收到的 action
對象必定是這種 Javascript Plain Object。
當 Thunk Middleware 處理了 Thunk 函數類型的 action
以後,若是有配置了其餘後續 Middlewares, 則將被跳過去而沒有機會執行。
例如:咱們的 Middlewares 配置爲 applyMiddleware(logger, thunk, timeoutScheduler)
,當 action
是 Thunk 函數時,這個 action
將沒有機會被 timeoutScheduler
Middleware 執行,而 logger
Middleware 則有機會在 thunk
Middleware 以前執行。每一個 Middleware 本身決定給不給後續 Middleware 處理的機會。
拼裝 Middlewares 的工具函數是 applyMiddleware
,該函數的模擬實現以下:
function applyMiddleware(store, middlewares) { middlewares = middlewares.slice(); middlewares.reverse(); let next = store.dispatch; middlewares.forEach(middleware => next = middleware(store)(next) ); return Object.assign({}, store, { dispatch: next }); }
結合 Middleware 的寫法:
const logger = store => next => action => { console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; };
咱們能夠看到,給 Middleware 傳入 store
和 next
以後,返回的是一個新的 dispatch
方法。而傳入的 next
參數則是以前 Middleware 返回的 dispatc
h 函數。這樣,在真正傳入 action
以前,咱們獲得了一個串聯在一塊兒的 dispatch
函數,該函數用來替代本來的store.dispatch
方法(經過 Object.assign(...)
)。Redux Middleware 機制的目的,就是以插件形式改變 store.dispatch
的行爲方式,從而可以處理不一樣類型的 action
輸入,獲得最終的 Javascript Plain Object 形式的 action
對象。
每個 Middleware 能夠獲得:
store
對象 (dispatch
屬性仍是原來的),所以,能夠經過 store.getState
得到最近的狀態,以及經過本來的 dispatch
對象直接發佈 action
對象,跳過其餘 Middleware dispatch
方法(next
)。上面 vanillaPromise
演示了這樣的用法。next
方法: 前一個Middleware 返回的 dispatch
方法。當前 Middleware 能夠根據本身對 action
的判斷和處理結果,決定是否調用 next
方法,以及傳入什麼樣的參數。以 newStore = applyMiddleware(logger,thunk,timeoutScheduler)(store))
這樣的聲明爲例,timeoutScheduler
獲得的next
參數就是原始的 store.dispatch
方法;thunk
擁有 timeoutScheduler
返回的 dispatch
方法,而 logger
又擁有 thunk
返回的 dispatch
方法。最後新生成的 newStore
的 dispatch
方法則是 logger
返回的。所以實際的 action
流動的順序先到 logger
返回的 dispatch
方法,再到 thunk
返回的 dispatch
方法,最後到 timeoutScheduler
返回的 dispatch
方法。
須要注意一點, logger
由於排在 dispatch
鏈條的第一個,所以能夠得到進入的每個 action
對象。可是因爲其餘 Middleware 有可能異步調用 dispatch
(異步調用前一個 Middleware 返回的 dispatch
方法或者原始的 store.dispatch
),所以,logger
並必定有機會知道 action
最終是怎麼傳遞的。
Middleware 能夠有不少玩法的,下面文檔列出了 Middleware 的原理和七種Middlewares:http://rackt.github.io/redux/docs/advanced/Middleware.html 。
store/reducer
是 Redux 的最核心邏輯,而 Middleware 是其外圍的一種擴展方式,僅負責 action
對象的產生。可是因爲 Redux 對於核心部分的限定很是嚴格(保持核心概念的簡單):例如,reducer 必須是同步的,實際工程需求所帶來的需求都被推到了 Dispatch/Middleware 這部分,官方文檔提到的使用方式則起到了」最佳實踐」的指導做用。
Middleware 是對 store.dispatch
方法的擴展機制。但有些時候則須要對整個 store
對象都進行擴充,這就引入了 Higher-Order Store 的概念。
這個概念和 React 的 Higher-Order Component 概念是相似的。https://github.com/gaearon/redux/blob/cdaa3e81ffdf49e25ce39eeed37affc8f0c590f7/docs/higher-order-stores.md ,既提供一個函數,接受 store
對象做爲輸入參數,產生一個新的 store
對象做爲返回值。
createStore => createStore'
Redux 建議你們在 Middleware 不能知足擴展要求的前提下再使用 Higher-Order Store,與 Redux 配套的 redux-devtools 就是一個例子。
上面的章節介紹了 Redux 的核心組組件和數據流程,能夠經過下圖回味一下:
┌──────────────┐ ┌─────────────┐ ┌──▶│ subReducer 1 │ ┌───▶│Middleware 1 │ │ └──────────────┘ │ └─────────────┘ │ │ │ │ │ ▼ ┌─────────────┐ │ │ ┌───────────────┐ ┌──────────┐ │ ┌──────────────┐ │ action' │────┘ ▼ ┌──▶│store.dispatch │───▶│ reducer │───┘ │ subReducer m │ └─────────────┘ ┌─────────────┐ │ └───────────────┘ └──────────┘ └──────────────┘ │Middleware n │ │ │ └─────────────┘ │ │ │ │ ▼ │ │ ┌──────────────┐ └──────────┘ │ state │ plain action └──────────────┘
Redux 解決的是應用程序狀態存儲以及如何變動的問題,至於怎麼用,則依賴於其餘模塊。關於如何在 React 或者 React-Native 中使用 Redux ,則須要參考 react-redux。
react-redux 是 React Components 如何使用 Redux 的 Binding。下面咱們來分析一個具體的例子。
import { Component } from 'react'; export default class Counter extends Component { render() { return ( <button onClick={this.props.onIncrement}> {this.props.value} </button> ); } }
這是一個 React Component,顯示了一個按鈕。按下這個按鈕,就會調用 this.props.onIncrement
。onIncrement
的具體內容在下面的例子中, 起做用爲每次調用 onIncrement
就會 dispatch
{type: INCREMENT}
Action 對象來更新 Store/State
。
在 react-redux 中,這樣的 Component 被稱爲 「Dumb」 Component,既其自己對 Redux 徹底無知,它只知道從 this.props
獲取須要的 Action Creator
而且瞭解其語義,適當的時候調用該方法。而 「Dumb」 Component 須要展示的外部數據也來自於 this.props
。
如何爲 「Dumb」 Component 準備 this.props
呢?react-redux 提供的 connect
函數幫助你完成這個功能:
import { Component } from 'react'; import { connect } from 'react-redux'; import Counter from '../components/Counter'; import { increment } from '../actionsCreators'; // Which part of the Redux global state does our component want to receive as props? function mapStateToProps(state) { return { value: state.counter }; } // Which action creators does it want to receive by props? function mapDispatchToProps(dispatch) { return { onIncrement: () => dispatch(increment()) }; } export default connect( // Line 20 mapStateToProps, mapDispatchToProps )(Counter);
第 20 行的 connect
將 state
的某個(些)屬性映射到了 Counter
Component 的 this.props
屬性中,同時也把針對特定的Action Creator
的 dispatch
方法傳遞給了 this.props
。這樣在 Counter
Component 中僅僅經過 this.props
就能夠完成 action dispatching 和 應用程序狀態獲取的動做。
若是 connect 函數省掉第二個參數,connect(mapStateToProps)(Counter)
,那麼 dispatch
方法會被直接傳遞給 this.props
。這不是推薦的方式,由於這意味着 Counter
須要瞭解 dispatch
的功能和語義了。
你能夠在你的組件樹的任何一個層次調用 connect
來爲下層組件綁定狀態和 dispatch
方法。可是僅在你的頂層組件調用 connect
進行綁定是首選的方法。
上面的例子其實是不可執行的,由於 connect
函數其實並無 Redux store
對象在哪裏。因此咱們須要有一個機制讓 connect
知道從你那裏得到 store
對象,這是經過 Provider
Component 來設定的,Provider Component
也是 react-redux 提供的工具組件。
React.render( <Provider store={store}> {() => <MyRootComponent />} </Provider>, rootEl );
Provider
Component 應該是你的 React Components 樹的根組件。因爲 React 0.13 版本的問題,Provider
Component 的子組件必須是一個函數,這個問題將在 React 0.14 中修復。
Provider
Component 和 connect
函數的配合,使得 React Component 在對 Redux 徹底無感的狀況下,僅經過 React 自身的機制來獲取和維護應用程序的狀態。
在上面的例子中,connect(mapStateToProps,mapDispatchToProps)(Counter)
中的 mapStateToProps
函數經過返回一個映射對象,指定了哪些 Store/State
屬性被映射到 React Component 的 this.props
,這個方法被稱爲 selector
。selector
的做用就是爲 React Components 構造適合本身須要的狀態視圖。selector
的引入,下降了 React Component 對 Store/State
數據結構的依賴,利於代碼解耦;同時因爲 selector
的實現徹底是自定義函數,所以也有足夠的靈活性(例如對原始狀態數據進行過濾、彙總等)。
reselect 這個項目提供了帶 cache 功能的 selector
。若是 Store/State
和構造 view 的參數沒有變化,那麼每次 Component 獲取的數據都未來自於上次調用/計算的結果。得益於 Store/State
Immutable 的本質,狀態變化的檢測是很是高效的。
Reducer
的執行是同步,產生的 State
是 Immutable 的。State
只能經過向 Reducer dispatch actions 來完成。State
的不一樣字段,能夠經過不一樣的 Reducers
來分別維護。combineReducers
負責組合這些 Reducers
,前提是每一個 Reducer
只能維護本身關心的字段。Action
對象只能是 Javascript Plain Object,可是經過在 store
上裝載 middleware
,則能夠任意定義 action
對象的形式,反正會有特定的 middleware
負責將此 action
對象變爲 Javascript Plain Object。能夠以middleware
鏈條爲集中點實現不少控制邏輯,例如 Log,Undo, ErrorHandler 等。reducer
、dispatch/middleware
是兩個經常使用擴展點、Higher-order Store 則僅針對須要擴展所有 Store
功能時使用。connect/selector
是擴展點,負責將 store
中的狀態添加到 React component
的 props
中。https://github.com/xgrommx/awesome-redux
加入 https://reactiflux.slack.com Team,而後選擇 redux channel。