Redux 核心概念
,文件在 。本文不是官方文件的翻譯。你可以在閱讀官方文件之前和之後閱讀本文,以加深其中的重點概念。
根據該專案原始碼的習慣,示例都是基於 ES2015 的語法來寫的。
是應用狀態管理服務。雖然本身受到了 很深的影響,但是其核心概念卻非常簡單,就是 Map/Reduce 中的 Reduce。
我們看一下 Javascript 中 Array.prototype.reduce
的用法:
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
。
Immutable State
Redux 認為,一個應用程式中,所有應用模組之間需要共享訪問的資料,都應該放在 State
物件中。這個應用模組可能是指 React Components,也可能是你自己訪問 AJAX API 的代理模組,具體是什麼並沒有一定的限制。State
以 “樹形” 的方式儲存應用程式的不同部分的資料。這些資料可能來自於網路呼叫、本地資料庫查詢、甚至包括當前某個 UI 元件的臨時執行狀態(只要是需要被不同模組訪問)、甚至當前視窗大小等。
Redux 沒有規定用什麼方式來儲存 State
,可能是 Javascript 物件,或者是 的資料結構。但是有一點,你最好確保 State 中每個節點都是 Immutable 的,這樣將確保 State 的消費者在判斷資料是否變化時,只要簡單地進行引用比較即可,例如:
newState.todos === prevState.todos
從而避免 Deep Equal 的遍歷過程。
為了確保這一點,在你的 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 keycounters = {...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 的。
在 Redux 中,State
只能透過 action
來變更。Reducer
就是根據 action
的語義來完成 State
變更的函式。Reducer
的執行是同步的。在給定 initState
以及一系列的 actions
,無論在什麼時間,重複執行多少次 Reducer
,都應該得到相同的 newState
。這使得你的應用程式的狀態是可以被 Log 以及 Replay 的。這種確定性,降低了前端開發所面臨的複雜狀態的亂入問題。確定的狀態、再加上 Hot-Reloaidng 和相應的 ,使得前端應用的可控性大大增強了。
State 結構設計
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 資料(現在常見的方式)往往是冗餘而非正規化的,因此,可能需要一些工具來幫助你轉換,例如: , 雖然很多時候自己控制會更有效一些。
Reducer
下面我們以熟悉 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 statefunction 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 了。 也可以幫我們完成類似的任務:
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
的狀態改變,則可以用上面的寫法。 組合(每個引數就是一個 Reducer
)的每一個 Reducer
都可以獲取整個 State
,所以請不要濫用(請參見相關討論: ),在大部分情況下,如果嚴格遵循資料正規化,透過計算的方法獲得跨越 Reducers
的狀態是推薦的方法( )。
一個 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]; } }
Store
在 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 5store.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 請求,就可以直接從當前頁面中直接獲得初始狀態。
Action
在 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
是必須要有的屬性,其他都是可選的。完整建議請參考 定義。已經有不少第三方模組是基於 FSA 的約定來開發了。
Action Creator
事實上,建立 action
物件很少用這種每次直接宣告物件的方式,更多地是透過一個建立函式。這個函式被稱為Action Creator
,例如:
function addTodo(text) { return { type: ADD_TODO, text }; }
Action Creator 看起來很簡單,但是如果結合上 Middleware 就可以變得非常靈活。
Middleware
如果你用過 ,那麼就會熟悉它的 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 = { getState } => next => action => { console.log('dispatching', action); let result = next(action); console.log('next state', 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
實現可以參見: 和 。同一個作者寫了兩個,後面這個支援 State
的差異顯示。
vanilla promise
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 = { getState, dispatch } => 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(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 的完整支援請參見: 。
Scheduled Dispatch
下面這個例子略微複雜一些,演示瞭如何延遲執行一個 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
如果不瞭解 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 處理的機會。
applyMiddleware
拼裝 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
物件,跳過其他 Middlewaredispatch
方法(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: 。
store/reducer
是 Redux 的最核心邏輯,而 Middleware 是其外圍的一種擴充套件方式,僅負責 action
物件的產生。但是由於 Redux 對於核心部分的限定非常嚴格(保持核心概念的簡單):例如,reducer 必須是同步的,實際工程需求所帶來的需求都被推到了 Dispatch/Middleware 這部分,官方文件提到的使用方式則起到了”最佳實踐”的指導作用。
Higher-Order Store
Middleware 是對 store.dispatch
方法的擴充套件機制。但有些時候則需要對整個 store
物件都進行擴充,這就引入了 Higher-Order Store 的概念。
這個概念和 React 的 概念是類似的。 ,既提供一個函式,接受 store
物件作為輸入引數,產生一個新的 store
物件作為返回值。
createStore => createStore'
Redux 建議大家在 Middleware 不能滿足擴充套件要求的前提下再使用 Higher-Order Store,與 Redux 配套的 redux-devtools 就是一個例子。
Binding To React (React-Native)
上面的章節介紹了 Redux 的核心組元件和資料流程,可以透過下圖回味一下:
┌──────────────┐ ┌─────────────┐ ┌──│ subReducer 1 │ ┌───│Middleware 1 │ │ └──────────────┘ │ └─────────────┘ │ │ │ │ │ ┌─────────────┐ │ │ ┌───────────────┐ ┌──────────┐ │ ┌──────────────┐ │ action' │────┘ ┌──│store.dispatch │───│ reducer │───┘ │ subReducer m │ └─────────────┘ ┌─────────────┐ │ └───────────────┘ └──────────┘ └──────────────┘ │Middleware n │ │ │ └─────────────┘ │ │ │ │ │ │ ┌──────────────┐ └──────────┘ │ state │ plain action └──────────────┘
Redux 解決的是應用程式狀態儲存以及如何變更的問題,至於怎麼用,則依賴於其他模組。關於如何在 React 或者 React-Native 中使用 Redux ,則需要參考。
是 React Components 如何使用 Redux 的 Binding。下面我們來分析一個具體的例子。
import { Component } from 'react';export default class Counter extends Component { render() { return ( ); } }
這是一個 React Component,顯示了一個按鈕。按下這個按鈕,就會呼叫 this.props.onIncrement
。onIncrement
的具體內容在下面的例子中, 起作用為每次呼叫 onIncrement
就會 dispatch
{type: INCREMENT}
Action 物件來更新 Store/State
。
在 中,這樣的 Component 被稱為 “Dumb” Component,既其本身對 Redux 完全無知,它只知道從 this.props
獲取需要的 Action Creator
並且瞭解其語義,適當的時候呼叫該方法。而 “Dumb” Component 需要展現的外部資料也來自於 this.props
。
如何為 “Dumb” Component 準備 this.props
呢? 提供的 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
的功能和語義了。
Components 的巢狀
你可以在你的元件樹的任何一個層次呼叫 connect
來為下層元件繫結狀態和 dispatch
方法。但是僅在你的頂層元件呼叫 connect
進行繫結是首選的方法。
Provider Component
上面的例子實際上是不可執行的,因為 connect
函式其實並沒有 Redux store
物件在哪裡。所以我們需要有一個機制讓 connect
知道從你那裡獲得 store
物件,這是透過 Provider
Component 來設定的,Provider Component
也是 提供的工具元件。
React.render({() => , rootEl );}
Provider
Component 應該是你的 React Components 樹的根元件。由於 React 0.13 版本的問題,Provider
Component 的子元件必須是一個函式,這個問題將在 React 0.14 中修復。
Provider
Component 和 connect
函式的配合,使得 React Component 在對 Redux 完全無感的情況下,僅透過 React 自身的機制來獲取和維護應用程式的狀態。
selector
在上面的例子中,connect(mapStateToProps,mapDispatchToProps)(Counter)
中的 mapStateToProps
函式透過返回一個對映物件,指定了哪些 Store/State
屬性被對映到 React Component 的 this.props
,這個方法被稱為 selector
。selector
的作用就是為 React Components 構造適合自己需要的狀態檢視。selector
的引入,降低了 React Component 對 Store/State
資料結構的依賴,利於程式碼解耦;同時由於 selector
的實現完全是自定義函式,因此也有足夠的靈活性(例如對原始狀態資料進行過濾、彙總等)。
這個專案提供了帶 cache 功能的 selector
。如果 Store/State
和構造 view 的引數沒有變化,那麼每次 Component 獲取的資料都將來自於上次呼叫/計算的結果。得益於 Store/State
Immutable 的本質,狀態變化的檢測是非常高效的。
總結
Redux 和 React 沒有直接關係,它瞄準的目標是應用狀態管理。
核心概念是 Map/Reduce 中的 Reduce。且
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 等。Redux 僅僅專注於應用狀態的維護,
reducer
、dispatch/middleware
是兩個常用擴充套件點、Higher-order Store 則僅針對需要擴充套件全部Store
功能時使用。react-redux 是 Redux 針對 React/React-Native 的 Binding,
connect/selector
是擴充套件點,負責將store
中的狀態新增到 Reactcomponent
的props
中。Redux 借用了很多函數語言程式設計的思想,瞭解函數語言程式設計會利於理解其實現原理,雖然使用它不需要了解很多函數語言程式設計的概念。和 Flux 相比,Redux 的概念更精簡、約定更嚴格、狀態更確定、而是擴充套件卻更靈活。
透過 可以獲得大量參考。
作者:jacobbubu
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2318/viewspace-2802728/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Redux 包教包會(一):介紹 Redux 三大核心概念Redux
- redux的基本概念Redux
- 核心概念
- 【RabbitMQ】核心概念MQ
- ES 核心概念
- Kubernetes核心概念
- Webpack核心概念解析Web
- Hyperledger Fabric 核心概念
- RPC核心概念理解RPC
- Laravel核心概念剖析Laravel
- ECS的核心概念
- Laravel中的核心概念Laravel
- Docker_02 核心概念Docker
- Spark 的核心概念 RDDSpark
- webpack(2)webpack核心概念Web
- laravel核心概念總結Laravel
- webpack (1)——核心概念的理解Web
- vuex核心概念---action、getter、moduleVue
- Kubernetes概念及核心物件物件
- vuex 中的核心概念及原理Vue
- k8s——核心概念篇K8S
- Spring原始碼系列:核心概念解析Spring原始碼
- 物件導向思想的核心概念物件
- PixiJS核心概念及簡單上手JS
- ElasticSearch核心概念和文件的CRUDElasticsearch
- 30分鐘理解GraphQL核心概念
- 3.k8s核心概念K8S
- Web教程:7個CSS核心概念WebCSS
- 【譯】Apache Storm系列 之一(核心概念)ApacheORM
- 33 個 JavaScript 核心概念系列(四): == 與 ===JavaScript
- Docker教程之三Docker核心概念Docker
- JavaScript核心概念(1):型別轉換JavaScript型別
- 領域驅動設計核心概念
- 資料中臺中的核心概念解析
- 計算機網路的核心概念計算機網路
- webpack 學習筆記:核心概念(上)Web筆記
- webpack 學習筆記:核心概念(下)Web筆記
- ZooKeeper 系列(一)—— ZooKeeper核心概念詳解