原文(保持更新):https://github.com/kenberkele...css
寫在前面
相信您已經看過 Redux 簡明教程,本教程是簡明教程的實戰化版本,伴隨源碼分析
Redux 用的是 ES6 編寫,看到有疑惑的地方的,能夠複製粘貼到這裏在線編譯 ES5html
在 Redux 的源碼目錄 src/
,咱們能夠看到以下文件結構:react
├── utils/ │ ├── warning.js # 打醬油的,負責在控制檯顯示警告信息 ├── applyMiddleware.js ├── bindActionCreators.js ├── combineReducers.js ├── compose.js ├── createStore.js ├── index.js # 入口文件
除去打醬油的 utils/warning.js
以及入口文件 index.js
,剩下那 5 個就是 Redux 的 APIgit
先說這個 API 的緣由是它沒有依賴,是一個純函數github
/** * 看起來逼格很高,實際運用實際上是這樣子的: * compose(f, g, h)(...arg) => f(g(h(...args))) * * 值得注意的是,它用到了 reduceRight,所以執行順序是從右到左 * * @param {多個函數,用逗號隔開} * @return {函數} */ export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) }
這裏的關鍵點在於,reduceRight
可傳入初始值:編程
// 因爲 reduce / reduceRight 僅僅是方向的不一樣,所以下面用 reduce 說明便可 var arr = [1, 2, 3, 4, 5] var re1 = arr.reduce(function(total, i) { return total + i }) console.log(re1) // 15 var re2 = arr.reduce(function(total, i) { return total + i }, 100) // <---------------傳入一個初始值 console.log(re2) // 115
下面是 compose
的實例(在線演示):redux
<!DOCTYPE html> <html> <head> <script src="//cdn.bootcss.com/redux/3.5.2/redux.min.js"></script> </head> <body> <script> function func1(num) { console.log('func1 得到參數 ' + num); return num + 1; } function func2(num) { console.log('func2 得到參數 ' + num); return num + 2; } function func3(num) { console.log('func3 得到參數 ' + num); return num + 3; } // 有點難看(若是函數名再長一點,那屏幕就不夠寬了) var re1 = func3(func2(func1(0))); console.log('re1:' + re1); console.log('==============='); // 很優雅 var re2 = Redux.compose(func3, func2, func1)(0); console.log('re2:' + re2); </script> </body> </html>
控制檯輸出:後端
func1 得到參數 0 func2 得到參數 1 func3 得到參數 3 re1:6 =============== func1 得到參數 0 func2 得到參數 1 func3 得到參數 3 re2:6
import isPlainObject from 'lodash/isPlainObject' import $$observable from 'symbol-observable' /** * 這是 Redux 的私有 action 常量 * 長得太醜了,你不要鳥就好了 */ export var ActionTypes = { INIT: '@@redux/INIT' } /** * @param {函數} reducer 很少解釋了 * @param {對象} preloadedState 主要用於先後端同構時的數據同步 * @param {函數} enhancer 很牛逼,能夠實現中間件、時間旅行,持久化等 * ※ Redux 僅提供 appleMiddleware 這個 Store Enhancer ※ * @return {Store} */ export default function createStore(reducer, preloadedState, enhancer) { // 這裏省略的代碼,到本文的最後再講述(用於壓軸你懂的) var currentReducer = reducer var currentState = preloadedState // 這就是整個應用的 state var currentListeners = [] // 用於存儲訂閱的回調函數,dispatch 後逐個執行 var nextListeners = currentListeners // 【懸念1:爲何須要兩個 存放回調函數 的變量?】 var isDispatching = false /** * 【懸念1·解疑】 * 試想,dispatch 後,回調函數正在乖乖地被逐個執行(for 循環進行時) * 假設回調函數隊列本來是這樣的 [a, b, c, d] * * 如今 for 循環執行到第 3 步,亦即 a、b 已經被執行,準備執行 c * 但在這電光火石的瞬間,a 被取消訂閱!!! * * 那麼此時回調函數隊列就變成了 [b, c, d] * 那麼第 3 步就對應換成了 d!!! * c 被跳過了!!!這就是躺槍。。。 * * 做爲一個回調函數,最大的恥辱就是得不到執行 * 所以爲了不這個問題,本函數會在上述場景中把 * currentListeners 複製給 nextListeners * * 這樣的話,dispatch 後,在逐個執行回調函數的過程當中 * 若是有新增訂閱或取消訂閱,都在 nextListeners 中操做 * 讓 currentListeners 中的回調函數得以完整地執行 * * 既然新增是在 nextListeners 中 push,所以毫無疑問 * 新的回調函數不會在本次 currentListeners 的循環體中被觸發 * * (上述事件發生的概率雖然很低,但仍是嚴謹點比較好) */ function ensureCanMutateNextListeners() { // <-------這貨就叫作【ensure 哥】吧 if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } /** * 返回 state */ function getState() { return currentState } /** * 負責註冊回調函數的老司機 * * 這裏須要注意的就是,回調函數中若是須要獲取 state * 那每次獲取都請使用 getState(),而不是開頭用一個變量緩存住它 * 由於回調函數執行期間,有可能有連續幾個 dispatch 讓 state 改得物是人非 * 並且別忘了,dispatch 以後,整個 state 是被徹底替換掉的 * 你緩存的 state 指向的可能已是老掉牙的 state 了!!! * * @param {函數} 想要訂閱的回調函數 * @return {函數} 取消訂閱的函數 */ function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected listener to be a function.') } var isSubscribed = true ensureCanMutateNextListeners() // 調用 ensure 哥保平安 nextListeners.push(listener) // 新增訂閱在 nextListeners 中操做 // 返回一個取消訂閱的函數 return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false ensureCanMutateNextListeners() // 調用 ensure 哥保平安 var index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) // 取消訂閱仍是在 nextListeners 中操做 } } /** * 改變應用狀態 state 的不二法門:dispatch 一個 action * 內部的實現是:往 reducer 中傳入 currentState 以及 action * 用其返回值替換 currentState,最後逐個觸發回調函數 * * 若是 dispatch 的不是一個對象類型的 action(同步的),而是 Promise / thunk(異步的) * 則需引入 redux-thunk 等中間件來反轉控制權【懸念2:什麼是反轉控制權?】 * * @param & @return {對象} action */ function dispatch(action) { if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true // 關鍵點:currentState 與 action 會流通到全部的 reducer // 全部 reducer 的返回值整合後,替換掉當前的 currentState currentState = currentReducer(currentState, action) } finally { isDispatching = false } // 令 currentListeners 等於 nextListeners,表示正在逐個執行回調函數(這就是上面 ensure 哥的斷定條件) var listeners = currentListeners = nextListeners // 逐個觸發回調函數。這裏不緩存數組長度是明智的,緣由見【懸念1·解疑】 for (var i = 0; i < listeners.length; i++) { listeners[i]() } return action // 爲了方便鏈式調用,dispatch 執行完畢後,返回 action(下文會提到的,稍微記住就行了) } /** * 替換當前 reducer 的老司機 * 主要用於代碼分離按需加載、熱替換等狀況 * * @param {函數} nextReducer */ function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer // 就是這麼簡單粗暴! dispatch({ type: ActionTypes.INIT }) // 觸發生成新的 state 樹 } /** * 這是留給 可觀察/響應式庫 的接口(詳情 https://github.com/zenparsing/es-observable) * 若是您瞭解 RxJS 等響應式編程庫,那可能會用到這個接口,不然請略過 * @return {observable} */ function observable() {略} // 這裏 dispatch 只是爲了生成 應用初始狀態 dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
【懸念2:什麼是反轉控制權? · 解疑】
在同步場景下,dispatch(action)
的這個 action
中的數據是同步獲取的,並無控制權的切換問題
但異步場景下,則須要將 dispatch
傳入到回調函數。待異步操做完成後,回調函數自行調用 dispatch(action)
api
說白了:在異步 Action Creator 中自行調用 dispatch
就至關於反轉控制權
您徹底能夠本身實現,也能夠藉助 redux-thunk / redux-promise 等中間件統一實現
(它們的做用也僅僅就是把 dispatch
等傳入異步 Action Creator 罷了)數組
拓展閱讀:阮老師的 Thunk 函數的含義與用法
題外話:您不以爲 JavaScript 的回調函數,就是反轉控制權最廣泛的體現嗎?
簡明教程中的 code-7
以下:
/** 本代碼塊記爲 code-7 **/ var initState = { counter: 0, todos: [] } function reducer(state, action) { if (!state) state = initState switch (action.type) { case 'ADD_TODO': var nextState = _.deepClone(state) // 用到了 lodash 的深克隆 nextState.todos.push(action.payload) return nextState default: return state } }
上面的 reducer
僅僅是實現了 「新增待辦事項」 的 state
的處理
咱們還有計數器的功能,下面咱們繼續增長計數器 「增長 1」 的功能:
/** 本代碼塊記爲 code-8 **/ var initState = { counter: 0, todos: [] } function reducer(state, action) { if (!state) return initState // 如果初始化可當即返回應用初始狀態 var nextState = _.deepClone(state) // 不然二話不說先克隆 switch (action.type) { case 'ADD_TODO': // 新增待辦事項 nextState.todos.push(action.payload) break case 'INCREMENT': // 計數器加 1 nextState.counter = nextState.counter + 1 break } return nextState }
若是說還有其餘的動做,都須要在 code-8
這個 reducer
中繼續堆砌處理邏輯
但咱們知道,計數器 與 待辦事項 屬於兩個不一樣的模塊,不該該都堆在一塊兒寫
若是以後又要引入新的模塊(例如留言板),該 reducer
會愈來愈臃腫
此時就是 combineReducers
大顯身手的時刻:
目錄結構以下 reducers/ ├── index.js ├── counterReducer.js ├── todosReducer.js
/** 本代碼塊記爲 code-9 **/ /* reducers/index.js */ import { combineReducers } from 'redux' import counterReducer from './counterReducer' import todosReducer from './todosReducer' const rootReducer = combineReducers({ counter: counterReducer, // <-------- 鍵名就是該 reducer 對應管理的 state todos: todosReducer }) export default rootReducer ------------------------------------------------- /* reducers/counterReducer.js */ export default function counterReducer(counter = 0, action) { // 傳入的 state 實際上是 state.counter switch (action.type) { case 'INCREMENT': return counter + 1 // counter 是值傳遞,所以能夠直接返回一個值 default: return counter } } ------------------------------------------------- /* reducers/todosReducers */ export default function todosReducer(todos = [], action) { // 傳入的 state 實際上是 state.todos switch (action.type) { case 'ADD_TODO': return [ ...todos, action.payload ] default: return todos } }
code-8 reducer
與 code-9 rootReducer
的功能是同樣的,但後者的各個子 reducer
僅維護對應的那部分 state
其可操做性、可維護性、可擴展性大大加強
Flux 中是根據不一樣的功能拆分出多個
store
分而治之
而 Redux 只容許應用中有惟一的store
,經過拆分出多個reducer
分別管理對應的state
下面繼續來深刻使用 combineReducers
。一直以來咱們的應用狀態都是隻有兩層,以下所示:
state ├── counter: 0 ├── todos: []
若是說如今又有一個需求:在待辦事項模塊中,存儲用戶每次操做(增刪改)的時間,那麼此時應用初始狀態樹應爲:
state ├── counter: 0 ├── todo ├── optTime: [] ├── todoList: [] # 這其實就是原來的 todos!
那麼對應的 reducer
就是:
目錄結構以下 reducers/ ├── index.js <-------------- combineReducers (生成 rootReducer) ├── counterReducer.js ├── todoReducers/ <--------- combineReducers ├── index.js ├── optTimeReducer.js ├── todoListReducer.js
/* reducers/index.js */ import { combineReducers } from 'redux' import counterReducer from './counterReducer' import todoReducers from './todoReducers/' const rootReducer = combineReducers({ counter: counterReducer, todo: todoReducers }) export default rootReducer ================================================= /* reducers/todoReducers/index.js */ import { combineReducers } from 'redux' import optTimeReducer from './optTimeReducer' import todoListReducer from './todoListReducer' const todoReducers = combineReducers({ optTime: optTimeReducer, todoList: todoListReducer }) export default todoReducers ------------------------------------------------- /* reducers/todosReducers/optTimeReducer.js */ export default function optTimeReducer(optTime = [], action) { // 咦?這裏怎麼沒有 switch-case 分支?誰說 reducer 就必定包含 switch-case 分支的? return action.type.includes('TODO') ? [ ...optTime, new Date() ] : optTime } ------------------------------------------------- /* reducers/todosReducers/todoListReducer.js */ export default function todoListReducer(todoList = [], action) { switch (action.type) { case 'ADD_TODO': return [ ...todoList, action.payload ] default: return todoList } }
不管您的應用狀態樹有多麼的複雜,均可以經過逐層下分管理對應部分的 state
:
counterReducer(counter, action) -------------------- counter ↗ ↘ rootReducer(state, action) —→∑ ↗ optTimeReducer(optTime, action) ------ optTime ↘ nextState ↘—→∑ todo ↗ ↘ todoListReducer(todoList,action) ----- todoList ↗ 注:左側表示 dispatch 分發流,∑ 表示 combineReducers;右側表示各實體 reducer 的返回值,最後彙總整合成 nextState
看了上圖,您應該能直觀感覺到爲什麼取名爲 reducer
了吧?把 state
分而治之,極大減輕開發與維護的難度
不管是
dispatch
哪一個action
,都會流通全部的reducer
表面上看來,這樣子很浪費性能,但 JavaScript 對於這種純函數的調用是很高效率的,所以請儘管放心
這也是爲什麼reducer
必須返回其對應的state
的緣由。不然整合狀態樹時,該reducer
對應的鍵名就是undefined
僅截取關鍵部分,畢竟有很大一部分都是類型檢測警告
function combineReducers(reducers) { var reducerKeys = Object.keys(reducers) var finalReducers = {} for (var i = 0; i < reducerKeys.length; i++) { var key = reducerKeys[i] if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } var finalReducerKeys = Object.keys(finalReducers) // 返回合成後的 reducer return function combination(state = {}, action) { var hasChanged = false var nextState = {} for (var i = 0; i < finalReducerKeys.length; i++) { var key = finalReducerKeys[i] var reducer = finalReducers[key] var previousStateForKey = state[key] // 獲取當前子 state var nextStateForKey = reducer(previousStateForKey, action) // 執行各子 reducer 中獲取子 nextState nextState[key] = nextStateForKey // 將子 nextState 掛載到對應的鍵名 hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
在此個人註釋不多,由於代碼寫得實在是太過明瞭了,註釋反而影響閱讀
做者 Dan 用了大量的for
循環,的確有點不夠優雅
這個 API 有點雞肋,它無非就是作了這件事情:
dispatch(ActionCreator(XXX))
/* 爲 Action Creator 加裝上自動 dispatch 技能 */ function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) } export default function bindActionCreators(actionCreators, dispatch) { // 省去一大坨類型判斷 var keys = Object.keys(actionCreators) var boundActionCreators = {} for (var i = 0; i < keys.length; i++) { var key = keys[i] var actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { // 逐個裝上自動 dispatch 技能 boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators }
簡明教程中的 code-5
以下:
<--! 本代碼塊記爲 code-5 --> <input id="todoInput" type="text" /> <button id="btn">提交</button> <script> $('#btn').on('click', function() { var content = $('#todoInput').val() // 獲取輸入框的值 var action = addTodo(content) // 執行 Action Creator 得到 action store.dispatch(action) // 手動顯式 dispatch 一個 action }) </script>
咱們看到,調用 addTodo
這個 Action Creator 後獲得一個 action
,以後又要手動 dispatch(action)
若是是隻有一個兩個 Action Creator 仍是能夠接受,但若是有不少個那就顯得有點重複了(其實我以爲不重複哈哈哈)
這個時候咱們就能夠利用 bindActionCreators
實現自動 dispatch
:
<input id="todoInput" type="text" /> <button id="btn">提交</button> <script> // 全局引入 Redux、jQuery,同時 store 是全局變量 var actionsCreators = Redux.bindActionCreators( { addTodo: addTodo }, store.dispatch // 傳入 dispatch 函數 ) $('#btn').on('click', function() { var content = $('#todoInput').val() actionCreators.addTodo(content) // 它會自動 dispatch }) </script>
綜上,這個 API 沒啥卵用,尤爲是異步場景下,基本用不上
Redux 中文文檔 高級 · Middleware 有提到中間件的演化由來
首先要理解何謂 Middleware
,何謂 Enhancer
說白了,Redux 引入中間件機制,其實就是爲了在 dispatch
先後,統一「作愛作的事」。。。
諸如統一的日誌記錄、引入 thunk 統一處理異步 Action Creator 等都屬於中間件
下面是一個簡單的打印動做先後 state
的中間件:
/* 裝逼寫法 */ const printStateMiddleware = ({ getState }) => next => action => { console.log('state before dispatch', getState()) let returnValue = next(action) console.log('state after dispatch', getState()) return returnValue } ------------------------------------------------- /* 下降逼格寫法 */ function printStateMiddleware(middlewareAPI) { // 記爲【錨點-1】,中間件內可用的 API return function (dispatch) { // 記爲【錨點-2】,傳入原 dispatch 的引用 return function (action) { console.log('state before dispatch', middlewareAPI.getState()) var returnValue = dispatch(action) // 還記得嗎,dispatch 的返回值其實仍是 action console.log('state after dispatch', middlewareAPI.getState()) return returnValue // 繼續傳給下一個中間件做爲參數 action } } }
說白了,Store 加強器就是對生成的 store
API 進行改造,這是它與中間件最大的區別(中間件不修改 store
的 API)
而改造 store
的 API 就要從它的締造者 createStore
入手。例如,Redux 的 API applyMiddleware
就是一個 Store 加強器:
import compose from './compose' // 這貨的做用其實就是 compose(f, g, h)(action) => f(g(h(action))) /* 傳入一坨中間件 */ export default function applyMiddleware(...middlewares) { /* 傳入 createStore */ return function(createStore) { /* 返回一個函數簽名跟 createStore 如出一轍的函數,亦即返回的是一個加強版的 createStore */ return function(reducer, preloadedState, enhancer) { // 用原 createStore 先生成一個 store,其包含 getState / dispatch / subscribe / replaceReducer 四個 API var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch // 指向原 dispatch var chain = [] // 存儲中間件的數組 // 提供給中間件的 API(其實都是 store 的 API) var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } // 給中間件「裝上」 API,見上面 ⊙Middleware【下降逼格寫法】的【錨點-1】 chain = middlewares.map(middleware => middleware(middlewareAPI)) // 串聯各個中間件,爲各個中間件傳入原 store.dispatch,見【下降逼格寫法】的【錨點-2】 dispatch = compose(...chain)(store.dispatch) return { ...store, // store 的 API 中保留 getState / subsribe / replaceReducer dispatch // 新 dispatch 覆蓋原 dispatch,日後調用 dispatch 就會觸發 chain 內的中間件鏈式串聯執行 } } } }
最終返回的雖然仍是 store
的那四個 API,但其中的 dispatch
函數的功能被加強了,這就是所謂的 Store Enhancer
<!DOCTYPE html> <html> <head> <script src="//cdn.bootcss.com/redux/3.5.2/redux.min.js"></script> </head> <body> <script> /** Action Creators */ function inc() { return { type: 'INCREMENT' }; } function dec() { return { type: 'DECREMENT' }; } function reducer(state, action) { state = state || { counter: 0 }; switch (action.type) { case 'INCREMENT': return { counter: state.counter + 1 }; case 'DECREMENT': return { counter: state.counter - 1 }; default: return state; } } function printStateMiddleware(middlewareAPI) { return function (dispatch) { return function (action) { console.log('dispatch 前:', middlewareAPI.getState()); var returnValue = dispatch(action); console.log('dispatch 後:', middlewareAPI.getState(), '\n'); return returnValue; }; }; } var enhancedCreateStore = Redux.applyMiddleware(printStateMiddleware)(Redux.createStore); var store = enhancedCreateStore(reducer); store.dispatch(inc()); store.dispatch(inc()); store.dispatch(dec()); </script> </body> </html>
控制檯輸出:
dispatch 前:{ counter: 0 } dispatch 後:{ counter: 1 } dispatch 前:{ counter: 1 } dispatch 後:{ counter: 2 } dispatch 前:{ counter: 2 } dispatch 後:{ counter: 1 }
實際上,上面生成 store
的代碼能夠更加優雅:
/** 本代碼塊記爲 code-10 **/ var store = Redux.createStore( reducer, Redux.applyMiddleware(printStateMiddleware) )
若是有多箇中間件以及多個加強器,還能夠這樣寫(請留意序號順序):
重溫一下
createStore
完整的函數簽名:function createStore(reducer, preloadedState, enhancer)
/** 本代碼塊記爲 code-11 **/ import { createStore, applyMiddleware, compose } from 'redux' const store = createStore( reducer, preloadedState, // <----- 可選,先後端同構的數據同步 compose( // <------------ 還記得嗎?compose 是從右到左的哦! applyMiddleware( // <-- 這貨也是 Store Enhancer 哦!但這是關乎中間件的加強器,必須置於 compose 執行鏈的最後 middleware1, middleware2, middleware3 ), enhancer3, enhancer2, enhancer1 ) )
爲何會支持那麼多種寫法呢?在 createStore
的源碼分析的開頭部分,我省略了一些代碼,如今奉上該壓軸部分:
/** 本代碼塊記爲 code-12 **/ if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { // 這裏就是上面 code-10 的狀況,只傳入 reducer 和 Store Enhancer 這兩個參數 enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } // 存在 enhancer 就當即執行,返回加強版的 createStore <--------- 記爲【錨點 12-1】 return enhancer(createStore)(reducer, preloadedState) } if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') } // 除 compose 外,createStore 居然也在此爲咱們提供了書寫的便利與自由度,實在是太體貼了
若是像 code-11
那樣有多個 enhancer
,則 code-12 【錨點 12-1】
中的代碼會執行屢次
生成最終的超級加強版 store
。最後,奉上 code-11
中 compose
內部的執行順序示意圖:
原 createStore ———— │ ↓ return enhancer1(createStore)(reducer, preloadedState, enhancer2) | ├———————→ createStore 加強版 1 │ ↓ return enhancer2(createStore1)(reducer, preloadedState, enhancer3) | ├———————————→ createStore 加強版 1+2 │ ↓ return enhancer3(createStore1+2)(reducer, preloadedState, applyMiddleware(m1,m2,m3)) | ├————————————————————→ createStore 加強版 1+2+3 │ ↓ return appleMiddleware(m1,m2,m3)(createStore1+2+3)(reducer, preloadedState) | ├——————————————————————————————————→ 生成最終加強版 store
Redux 有五個 API,分別是:
createStore(reducer, [initialState])
combineReducers(reducers)
applyMiddleware(...middlewares)
bindActionCreators(actionCreators, dispatch)
compose(...functions)
createStore
生成的 store
有四個 API,分別是:
getState()
dispatch(action)
subscribe(listener)
replaceReducer(nextReducer)
至此,若您已經理解上述 API 的做用機理,以及中間件與加強器的概念/區別
本人將不勝榮幸,不妨點個 star 算是對個人讚揚
如您對本教程有任何意見或改進的建議,歡迎 issue,我會盡快予您答覆
最後奉上 React + Redux + React Router 的簡易留言板實例:react-demo
拓展閱讀:中間件的洋蔥模型