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