摘要:進(jìn)階教程原文保持更新寫在前面相信您已經(jīng)看過(guò)簡(jiǎn)明教程,本教程是簡(jiǎn)明教程的實(shí)戰(zhàn)化版本,伴隨源碼分析用的是編寫,看到有疑惑的地方的,可以復(fù)制粘貼到這里在線編譯總覽在的源碼目錄,我們可以看到如下文件結(jié)構(gòu)打醬油的,負(fù)責(zé)在控制臺(tái)顯示警告信息入口文件除去
Redux 進(jìn)階教程
§ Redux API 總覽原文(保持更新):https://github.com/kenberkele...
寫在前面相信您已經(jīng)看過(guò) Redux 簡(jiǎn)明教程,本教程是簡(jiǎn)明教程的實(shí)戰(zhàn)化版本,伴隨源碼分析
Redux 用的是 ES6 編寫,看到有疑惑的地方的,可以復(fù)制粘貼到這里在線編譯 ES5
在 Redux 的源碼目錄 src/,我們可以看到如下文件結(jié)構(gòu):
├── utils/ │ ├── warning.js # 打醬油的,負(fù)責(zé)在控制臺(tái)顯示警告信息 ├── applyMiddleware.js ├── bindActionCreators.js ├── combineReducers.js ├── compose.js ├── createStore.js ├── index.js # 入口文件
除去打醬油的 utils/warning.js 以及入口文件 index.js,剩下那 5 個(gè)就是 Redux 的 API
§ compose(...functions)⊙ 源碼分析先說(shuō)這個(gè) API 的原因是它沒(méi)有依賴,是一個(gè)純函數(shù)
/** * 看起來(lái)逼格很高,實(shí)際運(yùn)用其實(shí)是這樣子的: * compose(f, g, h)(...arg) => f(g(h(...args))) * * 值得注意的是,它用到了 reduceRight,因此執(zhí)行順序是從右到左 * * @param {多個(gè)函數(shù),用逗號(hào)隔開} * @return {函數(shù)} */ 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)) }
這里的關(guān)鍵點(diǎn)在于,reduceRight 可傳入初始值:
// 由于 reduce / reduceRight 僅僅是方向的不同,因此下面用 reduce 說(shuō)明即可 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) // <---------------傳入一個(gè)初始值 console.log(re2) // 115
下面是 compose 的實(shí)例(在線演示):
控制臺(tái)輸出:
func1 獲得參數(shù) 0 func2 獲得參數(shù) 1 func3 獲得參數(shù) 3 re1:6 =============== func1 獲得參數(shù) 0 func2 獲得參數(shù) 1 func3 獲得參數(shù) 3 re2:6§ createStore(reducer, initialState, enhancer) ⊙ 源碼分析
import isPlainObject from "lodash/isPlainObject" import $$observable from "symbol-observable" /** * 這是 Redux 的私有 action 常量 * 長(zhǎng)得太丑了,你不要鳥就行了 */ export var ActionTypes = { INIT: "@@redux/INIT" } /** * @param {函數(shù)} reducer 不多解釋了 * @param {對(duì)象} preloadedState 主要用于前后端同構(gòu)時(shí)的數(shù)據(jù)同步 * @param {函數(shù)} enhancer 很牛逼,可以實(shí)現(xiàn)中間件、時(shí)間旅行,持久化等 * ※ Redux 僅提供 appleMiddleware 這個(gè) Store Enhancer ※ * @return {Store} */ export default function createStore(reducer, preloadedState, enhancer) { // 這里省略的代碼,到本文的最后再講述(用于壓軸你懂的) var currentReducer = reducer var currentState = preloadedState // 這就是整個(gè)應(yīng)用的 state var currentListeners = [] // 用于存儲(chǔ)訂閱的回調(diào)函數(shù),dispatch 后逐個(gè)執(zhí)行 var nextListeners = currentListeners // 【懸念1:為什么需要兩個(gè) 存放回調(diào)函數(shù) 的變量?】 var isDispatching = false /** * 【懸念1·解疑】 * 試想,dispatch 后,回調(diào)函數(shù)正在乖乖地被逐個(gè)執(zhí)行(for 循環(huán)進(jìn)行時(shí)) * 假設(shè)回調(diào)函數(shù)隊(duì)列原本是這樣的 [a, b, c, d] * * 現(xiàn)在 for 循環(huán)執(zhí)行到第 3 步,亦即 a、b 已經(jīng)被執(zhí)行,準(zhǔn)備執(zhí)行 c * 但在這電光火石的瞬間,a 被取消訂閱?。?! * * 那么此時(shí)回調(diào)函數(shù)隊(duì)列就變成了 [b, c, d] * 那么第 3 步就對(duì)應(yīng)換成了 d!?。? * c 被跳過(guò)了?。?!這就是躺槍。。。 * * 作為一個(gè)回調(diào)函數(shù),最大的恥辱就是得不到執(zhí)行 * 因此為了避免這個(gè)問(wèn)題,本函數(shù)會(huì)在上述場(chǎng)景中把 * currentListeners 復(fù)制給 nextListeners * * 這樣的話,dispatch 后,在逐個(gè)執(zhí)行回調(diào)函數(shù)的過(guò)程中 * 如果有新增訂閱或取消訂閱,都在 nextListeners 中操作 * 讓 currentListeners 中的回調(diào)函數(shù)得以完整地執(zhí)行 * * 既然新增是在 nextListeners 中 push,因此毫無(wú)疑問(wèn) * 新的回調(diào)函數(shù)不會(huì)在本次 currentListeners 的循環(huán)體中被觸發(fā) * * (上述事件發(fā)生的幾率雖然很低,但還是嚴(yán)謹(jǐn)點(diǎn)比較好) */ function ensureCanMutateNextListeners() { // <-------這貨就叫做【ensure 哥】吧 if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } /** * 返回 state */ function getState() { return currentState } /** * 負(fù)責(zé)注冊(cè)回調(diào)函數(shù)的老司機(jī) * * 這里需要注意的就是,回調(diào)函數(shù)中如果需要獲取 state * 那每次獲取都請(qǐng)使用 getState(),而不是開頭用一個(gè)變量緩存住它 * 因?yàn)榛卣{(diào)函數(shù)執(zhí)行期間,有可能有連續(xù)幾個(gè) dispatch 讓 state 改得物是人非 * 而且別忘了,dispatch 之后,整個(gè) state 是被完全替換掉的 * 你緩存的 state 指向的可能已經(jīng)是老掉牙的 state 了?。。? * * @param {函數(shù)} 想要訂閱的回調(diào)函數(shù) * @return {函數(shù)} 取消訂閱的函數(shù) */ function subscribe(listener) { if (typeof listener !== "function") { throw new Error("Expected listener to be a function.") } var isSubscribed = true ensureCanMutateNextListeners() // 調(diào)用 ensure 哥保平安 nextListeners.push(listener) // 新增訂閱在 nextListeners 中操作 // 返回一個(gè)取消訂閱的函數(shù) return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false ensureCanMutateNextListeners() // 調(diào)用 ensure 哥保平安 var index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) // 取消訂閱還是在 nextListeners 中操作 } } /** * 改變應(yīng)用狀態(tài) state 的不二法門:dispatch 一個(gè) action * 內(nèi)部的實(shí)現(xiàn)是:往 reducer 中傳入 currentState 以及 action * 用其返回值替換 currentState,最后逐個(gè)觸發(fā)回調(diào)函數(shù) * * 如果 dispatch 的不是一個(gè)對(duì)象類型的 action(同步的),而是 Promise / thunk(異步的) * 則需引入 redux-thunk 等中間件來(lái)反轉(zhuǎn)控制權(quán)【懸念2:什么是反轉(zhuǎn)控制權(quán)?】 * * @param & @return {對(duì)象} 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 // 關(guān)鍵點(diǎn):currentState 與 action 會(huì)流通到所有的 reducer // 所有 reducer 的返回值整合后,替換掉當(dāng)前的 currentState currentState = currentReducer(currentState, action) } finally { isDispatching = false } // 令 currentListeners 等于 nextListeners,表示正在逐個(gè)執(zhí)行回調(diào)函數(shù)(這就是上面 ensure 哥的判定條件) var listeners = currentListeners = nextListeners // 逐個(gè)觸發(fā)回調(diào)函數(shù)。這里不緩存數(shù)組長(zhǎng)度是明智的,原因見【懸念1·解疑】 for (var i = 0; i < listeners.length; i++) { listeners[i]() } return action // 為了方便鏈?zhǔn)秸{(diào)用,dispatch 執(zhí)行完畢后,返回 action(下文會(huì)提到的,稍微記住就好了) } /** * 替換當(dāng)前 reducer 的老司機(jī) * 主要用于代碼分離按需加載、熱替換等情況 * * @param {函數(shù)} nextReducer */ function replaceReducer(nextReducer) { if (typeof nextReducer !== "function") { throw new Error("Expected the nextReducer to be a function.") } currentReducer = nextReducer // 就是這么簡(jiǎn)單粗暴! dispatch({ type: ActionTypes.INIT }) // 觸發(fā)生成新的 state 樹 } /** * 這是留給 可觀察/響應(yīng)式庫(kù) 的接口(詳情 https://github.com/zenparsing/es-observable) * 如果您了解 RxJS 等響應(yīng)式編程庫(kù),那可能會(huì)用到這個(gè)接口,否則請(qǐng)略過(guò) * @return {observable} */ function observable() {略} // 這里 dispatch 只是為了生成 應(yīng)用初始狀態(tài) dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
【懸念2:什么是反轉(zhuǎn)控制權(quán)? · 解疑】
在同步場(chǎng)景下,dispatch(action) 的這個(gè) action 中的數(shù)據(jù)是同步獲取的,并沒(méi)有控制權(quán)的切換問(wèn)題
但異步場(chǎng)景下,則需要將 dispatch 傳入到回調(diào)函數(shù)。待異步操作完成后,回調(diào)函數(shù)自行調(diào)用 dispatch(action)
說(shuō)白了:在異步 Action Creator 中自行調(diào)用 dispatch 就相當(dāng)于反轉(zhuǎn)控制權(quán)
您完全可以自己實(shí)現(xiàn),也可以借助 redux-thunk / redux-promise 等中間件統(tǒng)一實(shí)現(xiàn)
(它們的作用也僅僅就是把 dispatch 等傳入異步 Action Creator 罷了)
§ combineReducers(reducers) ⊙ 應(yīng)用場(chǎng)景拓展閱讀:阮老師的 Thunk 函數(shù)的含義與用法
題外話:您不覺(jué)得 JavaScript 的回調(diào)函數(shù),就是反轉(zhuǎn)控制權(quán)最普遍的體現(xiàn)嗎?
簡(jiǎn)明教程中的 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 僅僅是實(shí)現(xiàn)了 “新增待辦事項(xiàng)” 的 state 的處理
我們還有計(jì)數(shù)器的功能,下面我們繼續(xù)增加計(jì)數(shù)器 “增加 1” 的功能:
/** 本代碼塊記為 code-8 **/ var initState = { counter: 0, todos: [] } function reducer(state, action) { if (!state) return initState // 若是初始化可立即返回應(yīng)用初始狀態(tài) var nextState = _.deepClone(state) // 否則二話不說(shuō)先克隆 switch (action.type) { case "ADD_TODO": // 新增待辦事項(xiàng) nextState.todos.push(action.payload) break case "INCREMENT": // 計(jì)數(shù)器加 1 nextState.counter = nextState.counter + 1 break } return nextState }
如果說(shuō)還有其他的動(dòng)作,都需要在 code-8 這個(gè) reducer 中繼續(xù)堆砌處理邏輯
但我們知道,計(jì)數(shù)器 與 待辦事項(xiàng) 屬于兩個(gè)不同的模塊,不應(yīng)該都堆在一起寫
如果之后又要引入新的模塊(例如留言板),該 reducer 會(huì)越來(lái)越臃腫
此時(shí)就是 combineReducers 大顯身手的時(shí)刻:
目錄結(jié)構(gòu)如下 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 對(duì)應(yīng)管理的 state todos: todosReducer }) export default rootReducer ------------------------------------------------- /* reducers/counterReducer.js */ export default function counterReducer(counter = 0, action) { // 傳入的 state 其實(shí)是 state.counter switch (action.type) { case "INCREMENT": return counter + 1 // counter 是值傳遞,因此可以直接返回一個(gè)值 default: return counter } } ------------------------------------------------- /* reducers/todosReducers */ export default function todosReducer(todos = [], action) { // 傳入的 state 其實(shí)是 state.todos switch (action.type) { case "ADD_TODO": return [ ...todos, action.payload ] default: return todos } }
code-8 reducer 與 code-9 rootReducer 的功能是一樣的,但后者的各個(gè)子 reducer 僅維護(hù)對(duì)應(yīng)的那部分 state
其可操作性、可維護(hù)性、可擴(kuò)展性大大增強(qiáng)
Flux 中是根據(jù)不同的功能拆分出多個(gè) store 分而治之
而 Redux 只允許應(yīng)用中有唯一的 store,通過(guò)拆分出多個(gè) reducer 分別管理對(duì)應(yīng)的 state
下面繼續(xù)來(lái)深入使用 combineReducers。一直以來(lái)我們的應(yīng)用狀態(tài)都是只有兩層,如下所示:
state ├── counter: 0 ├── todos: []
如果說(shuō)現(xiàn)在又有一個(gè)需求:在待辦事項(xiàng)模塊中,存儲(chǔ)用戶每次操作(增刪改)的時(shí)間,那么此時(shí)應(yīng)用初始狀態(tài)樹應(yīng)為:
state ├── counter: 0 ├── todo ├── optTime: [] ├── todoList: [] # 這其實(shí)就是原來(lái)的 todos!
那么對(duì)應(yīng)的 reducer 就是:
目錄結(jié)構(gòu)如下 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) { // 咦?這里怎么沒(méi)有 switch-case 分支?誰(shuí)說(shuō) 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 } }
無(wú)論您的應(yīng)用狀態(tài)樹有多么的復(fù)雜,都可以通過(guò)逐層下分管理對(duì)應(yīng)部分的 state:
counterReducer(counter, action) -------------------- counter ↗ ↘ rootReducer(state, action) —→∑ ↗ optTimeReducer(optTime, action) ------ optTime ↘ nextState ↘—→∑ todo ↗ ↘ todoListReducer(todoList,action) ----- todoList ↗ 注:左側(cè)表示 dispatch 分發(fā)流,∑ 表示 combineReducers;右側(cè)表示各實(shí)體 reducer 的返回值,最后匯總整合成 nextState
看了上圖,您應(yīng)該能直觀感受到為何取名為 reducer 了吧?把 state 分而治之,極大減輕開發(fā)與維護(hù)的難度
⊙ 源碼分析無(wú)論是 dispatch 哪個(gè) action,都會(huì)流通所有的 reducer
表面上看來(lái),這樣子很浪費(fèi)性能,但 JavaScript 對(duì)于這種純函數(shù)的調(diào)用是很高效率的,因此請(qǐng)盡管放心
這也是為何 reducer 必須返回其對(duì)應(yīng)的 state 的原因。否則整合狀態(tài)樹時(shí),該 reducer 對(duì)應(yīng)的鍵名就是 undefined
僅截取關(guān)鍵部分,畢竟有很大一部分都是類型檢測(cè)警告
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] // 獲取當(dāng)前子 state var nextStateForKey = reducer(previousStateForKey, action) // 執(zhí)行各子 reducer 中獲取子 nextState nextState[key] = nextStateForKey // 將子 nextState 掛載到對(duì)應(yīng)的鍵名 hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
§ bindActionCreators(actionCreators, dispatch)在此我的注釋很少,因?yàn)榇a寫得實(shí)在是太過(guò)明了了,注釋反而影響閱讀
作者 Dan 用了大量的 for 循環(huán),的確有點(diǎn)不夠優(yōu)雅
⊙ 源碼分析這個(gè) API 有點(diǎn)雞肋,它無(wú)非就是做了這件事情:dispatch(ActionCreator(XXX))
/* 為 Action Creator 加裝上自動(dòng) 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") { // 逐個(gè)裝上自動(dòng) dispatch 技能 boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators }⊙ 應(yīng)用場(chǎng)景
簡(jiǎn)明教程中的 code-5 如下:
<--! 本代碼塊記為 code-5 -->
我們看到,調(diào)用 addTodo 這個(gè) Action Creator 后得到一個(gè) action,之后又要手動(dòng) dispatch(action)
如果是只有一個(gè)兩個(gè) Action Creator 還是可以接受,但如果有很多個(gè)那就顯得有點(diǎn)重復(fù)了(其實(shí)我覺(jué)得不重復(fù)哈哈哈)
這個(gè)時(shí)候我們就可以利用 bindActionCreators 實(shí)現(xiàn)自動(dòng) dispatch:
§ applyMiddleware(...middlewares)綜上,這個(gè) API 沒(méi)啥卵用,尤其是異步場(chǎng)景下,基本用不上
Redux 中文文檔 高級(jí) · Middleware 有提到中間件的演化由來(lái)
首先要理解何謂 Middleware,何謂 Enhancer
⊙ Middleware說(shuō)白了,Redux 引入中間件機(jī)制,其實(shí)就是為了在 dispatch 前后,統(tǒng)一“做愛(ài)做的事”。。。
諸如統(tǒng)一的日志記錄、引入 thunk 統(tǒng)一處理異步 Action Creator 等都屬于中間件
下面是一個(gè)簡(jiǎn)單的打印動(dòng)作前后 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) { // 記為【錨點(diǎn)-1】,中間件內(nèi)可用的 API return function (dispatch) { // 記為【錨點(diǎn)-2】,傳入原 dispatch 的引用 return function (action) { console.log("state before dispatch", middlewareAPI.getState()) var returnValue = dispatch(action) // 還記得嗎,dispatch 的返回值其實(shí)還是 action console.log("state after dispatch", middlewareAPI.getState()) return returnValue // 繼續(xù)傳給下一個(gè)中間件作為參數(shù) action } } }⊙ Store Enhancer
說(shuō)白了,Store 增強(qiáng)器就是對(duì)生成的 store API 進(jìn)行改造,這是它與中間件最大的區(qū)別(中間件不修改 store 的 API)
而改造 store 的 API 就要從它的締造者 createStore 入手。例如,Redux 的 API applyMiddleware 就是一個(gè) Store 增強(qiáng)器:
import compose from "./compose" // 這貨的作用其實(shí)就是 compose(f, g, h)(action) => f(g(h(action))) /* 傳入一坨中間件 */ export default function applyMiddleware(...middlewares) { /* 傳入 createStore */ return function(createStore) { /* 返回一個(gè)函數(shù)簽名跟 createStore 一模一樣的函數(shù),亦即返回的是一個(gè)增強(qiáng)版的 createStore */ return function(reducer, preloadedState, enhancer) { // 用原 createStore 先生成一個(gè) store,其包含 getState / dispatch / subscribe / replaceReducer 四個(gè) API var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch // 指向原 dispatch var chain = [] // 存儲(chǔ)中間件的數(shù)組 // 提供給中間件的 API(其實(shí)都是 store 的 API) var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } // 給中間件“裝上” API,見上面 ⊙Middleware【降低逼格寫法】的【錨點(diǎn)-1】 chain = middlewares.map(middleware => middleware(middlewareAPI)) // 串聯(lián)各個(gè)中間件,為各個(gè)中間件傳入原 store.dispatch,見【降低逼格寫法】的【錨點(diǎn)-2】 dispatch = compose(...chain)(store.dispatch) return { ...store, // store 的 API 中保留 getState / subsribe / replaceReducer dispatch // 新 dispatch 覆蓋原 dispatch,往后調(diào)用 dispatch 就會(huì)觸發(fā) chain 內(nèi)的中間件鏈?zhǔn)酱?lián)執(zhí)行 } } } }
最終返回的雖然還是 store 的那四個(gè) API,但其中的 dispatch 函數(shù)的功能被增強(qiáng)了,這就是所謂的 Store Enhancer
⊙ 綜合應(yīng)用 ( 在線演示 )控制臺(tái)輸出:
dispatch 前:{ counter: 0 } dispatch 后:{ counter: 1 } dispatch 前:{ counter: 1 } dispatch 后:{ counter: 2 } dispatch 前:{ counter: 2 } dispatch 后:{ counter: 1 }
實(shí)際上,上面生成 store 的代碼可以更加優(yōu)雅:
/** 本代碼塊記為 code-10 **/ var store = Redux.createStore( reducer, Redux.applyMiddleware(printStateMiddleware) )
如果有多個(gè)中間件以及多個(gè)增強(qiáng)器,還可以這樣寫(請(qǐng)留意序號(hào)順序):
重溫一下 createStore 完整的函數(shù)簽名:function createStore(reducer, preloadedState, enhancer)
/** 本代碼塊記為 code-11 **/ import { createStore, applyMiddleware, compose } from "redux" const store = createStore( reducer, preloadedState, // <----- 可選,前后端同構(gòu)的數(shù)據(jù)同步 compose( // <------------ 還記得嗎?compose 是從右到左的哦! applyMiddleware( // <-- 這貨也是 Store Enhancer 哦!但這是關(guān)乎中間件的增強(qiáng)器,必須置于 compose 執(zhí)行鏈的最后 middleware1, middleware2, middleware3 ), enhancer3, enhancer2, enhancer1 ) )
為什么會(huì)支持那么多種寫法呢?在 createStore 的源碼分析的開頭部分,我省略了一些代碼,現(xiàn)在奉上該壓軸部分:
/** 本代碼塊記為 code-12 **/ if (typeof preloadedState === "function" && typeof enhancer === "undefined") { // 這里就是上面 code-10 的情況,只傳入 reducer 和 Store Enhancer 這兩個(gè)參數(shù) enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== "undefined") { if (typeof enhancer !== "function") { throw new Error("Expected the enhancer to be a function.") } // 存在 enhancer 就立即執(zhí)行,返回增強(qiáng)版的 createStore <--------- 記為【錨點(diǎn) 12-1】 return enhancer(createStore)(reducer, preloadedState) } if (typeof reducer !== "function") { throw new Error("Expected the reducer to be a function.") } // 除 compose 外,createStore 竟然也在此為我們提供了書寫的便利與自由度,實(shí)在是太體貼了
如果像 code-11 那樣有多個(gè) enhancer,則 code-12 【錨點(diǎn) 12-1】 中的代碼會(huì)執(zhí)行多次
生成最終的超級(jí)增強(qiáng)版 store。最后,奉上 code-11 中 compose 內(nèi)部的執(zhí)行順序示意圖:
原 createStore ———— │ ↓ return enhancer1(createStore)(reducer, preloadedState, enhancer2) | ├———————→ createStore 增強(qiáng)版 1 │ ↓ return enhancer2(createStore1)(reducer, preloadedState, enhancer3) | ├———————————→ createStore 增強(qiáng)版 1+2 │ ↓ return enhancer3(createStore1+2)(reducer, preloadedState, applyMiddleware(m1,m2,m3)) | ├————————————————————→ createStore 增強(qiáng)版 1+2+3 │ ↓ return appleMiddleware(m1,m2,m3)(createStore1+2+3)(reducer, preloadedState) | ├——————————————————————————————————→ 生成最終增強(qiáng)版 store§ 總結(jié)
Redux 有五個(gè) API,分別是:
createStore(reducer, [initialState])
combineReducers(reducers)
applyMiddleware(...middlewares)
bindActionCreators(actionCreators, dispatch)
compose(...functions)
createStore 生成的 store 有四個(gè) API,分別是:
getState()
dispatch(action)
subscribe(listener)
replaceReducer(nextReducer)
至此,若您已經(jīng)理解上述 API 的作用機(jī)理,以及中間件與增強(qiáng)器的概念/區(qū)別
本人將不勝榮幸,不妨點(diǎn)個(gè) star 算是對(duì)我的贊賞
如您對(duì)本教程有任何意見或改進(jìn)的建議,歡迎 issue,我會(huì)盡快予您答復(fù)
最后奉上 React + Redux + React Router 的簡(jiǎn)易留言板實(shí)例:react-demo
拓展閱讀:中間件的洋蔥模型
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/80156.html
摘要:只要一個(gè)有,那無(wú)論用什么設(shè)備訪問(wèn),都會(huì)得到這個(gè)還原也是相當(dāng)簡(jiǎn)單把數(shù)據(jù)庫(kù)備份導(dǎo)入到另一臺(tái)機(jī)器,部署同樣的運(yùn)行環(huán)境與代碼。純粹只是一個(gè)狀態(tài)管理庫(kù),幾乎可以搭配任何框架使用上述例子連都沒(méi)用哦親下一章進(jìn)階教程 Redux 簡(jiǎn)明教程 原文鏈接(保持更新):https://github.com/kenberkele... 寫在前面 本教程深入淺出,配套 簡(jiǎn)明教程、進(jìn)階教程(源碼精讀)以及文檔注釋...
摘要:在函數(shù)式編程中,異步操作修改全局變量等與函數(shù)外部環(huán)境發(fā)生的交互叫做副作用通常認(rèn)為這些操作是邪惡骯臟的,并且也是導(dǎo)致的源頭。 注:這篇是17年1月的文章,搬運(yùn)自本人 blog... https://github.com/BuptStEve/... 零、前言 在上一篇中介紹了 Redux 的各項(xiàng)基礎(chǔ) api。接著一步一步地介紹如何與 React 進(jìn)行結(jié)合,并從引入過(guò)程中遇到的各個(gè)痛點(diǎn)引出 ...
摘要:前端進(jìn)階進(jìn)階構(gòu)建項(xiàng)目一配置最佳實(shí)踐狀態(tài)管理之痛點(diǎn)分析與改良開發(fā)中所謂狀態(tài)淺析從時(shí)間旅行的烏托邦,看狀態(tài)管理的設(shè)計(jì)誤區(qū)使用更好地處理數(shù)據(jù)愛(ài)彼迎房源詳情頁(yè)中的性能優(yōu)化從零開始,在中構(gòu)建時(shí)間旅行式調(diào)試用輕松管理復(fù)雜狀態(tài)如何把業(yè)務(wù)邏輯這個(gè)故事講好和 前端進(jìn)階 webpack webpack進(jìn)階構(gòu)建項(xiàng)目(一) Webpack 4 配置最佳實(shí)踐 react Redux狀態(tài)管理之痛點(diǎn)、分析與...
摘要:系列種優(yōu)化頁(yè)面加載速度的方法隨筆分類中個(gè)最重要的技術(shù)點(diǎn)常用整理網(wǎng)頁(yè)性能管理詳解離線緩存簡(jiǎn)介系列編寫高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問(wèn)性能優(yōu)化方案實(shí)現(xiàn)的大排序算法一怪對(duì)象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁(yè)面加載速度的方法 隨筆分類 - HTML5 HTML5中40個(gè)最重要的技術(shù)點(diǎn) 常用meta整理 網(wǎng)頁(yè)性能管理詳解 HTML5 ...
閱讀 2922·2023-04-26 02:14
閱讀 3773·2019-08-30 15:55
閱讀 1862·2019-08-29 16:42
閱讀 2773·2019-08-26 11:55
閱讀 2859·2019-08-23 13:38
閱讀 500·2019-08-23 12:10
閱讀 1322·2019-08-23 11:44
閱讀 2829·2019-08-23 11:43