已經快一年沒有碰過 React 全家桶了,最近換了個項目組要用到 React 技術棧,因此最近又複習了一下;撿起舊知識的同時又有了一些新的收穫,在這裏做文以記之。webpack
在閱讀文章以前,最好已經知道如何使用 Redux(不是 React-Redux)。git
爲了更好的解讀源碼,咱們能夠把源碼拷貝到本地,而後搭建一個開發環境。Redux 的使用不依賴於 React,因此你徹底能夠在一個極爲簡單的 JavaScript 項目中使用它。這裏再也不贅述開發環境的搭建過程,須要的同窗能夠直接拷貝個人代碼到本地,而後安裝依賴,運行項目。github
$ git clone https://github.com/zhongdeming428/redux && cd redux
$ npm i
$ npm run dev
複製代碼
忽略項目中的那些說明文檔什麼的,只看 src 這個源文件目錄,其結構以下:web
src
├── applyMiddleware.js // 應用中間件的 API
├── bindActionCreators.js // 轉換 actionCreators 的 API
├── combineReducers.js // 組合轉換 reducer 的 API
├── compose.js // 工具函數,用於嵌套調用中間件
├── createStore.js // 入口函數,建立 store 的 API
├── index.js // redux 項目的入口文件,用於統一暴露全部 API
├── test
│ └── index.js // 我所建立的用於調試的腳本
└── utils // 專門放工具函數的目錄
├── actionTypes.js // 定義了一些 redux 預留的 action type
├── isPlainObject.js // 用於判斷是不是純對象
└── warning.js // 用於拋出合適的警告信息
複製代碼
能夠看出來 redux 的源碼結構簡單清晰明瞭,幾個主要的(也是僅有的) API 被儘量的分散到了單個的文件模塊中,咱們只須要挨個的看就好了。npm
上一小節說到 index.js 是 redux 項目的入口文件,用於暴露全部的 API,因此咱們來看看代碼:redux
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
// 不一樣的 API 寫在不一樣的 js 文件中,最後經過 index.js 統一導出。
// 這個函數用於判斷當前代碼是否已經被打包工具(好比 Webpack)壓縮過,若是被壓縮過的話,
// isCrushed 函數的名稱會被替換掉。若是被替換了函數名可是 process.env.NODE_ENV 又不等於 production
// 的時候,提醒用戶使用生產環境下的精簡代碼。
function isCrushed() {}
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
warning(
'You are currently using minified code outside of NODE_ENV === "production". ' +
'This means that you are running a slower development build of Redux. ' +
'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
'to ensure you have the correct code for your production build.'
)
}
// 導出主要的 API。
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
複製代碼
我刪除了全部的英文註釋以減少篇幅,若是你們想看原來的註釋,能夠去 redux 的項目查看源碼。api
能夠看到在程序的頭部引入了全部的 API 模塊以及工具函數,而後在底部統一導出了。這一部分比較簡單,主要是 isCrushed 函數有點意思。做者用這個函數來判斷代碼是否被壓縮過(判斷函數名是否被替換掉了)。數組
這一部分也引用到了工具函數,因爲這幾個函數比較簡單,因此能夠先看看它們是幹嗎的。promise
除了 compose 函數之外,全部的工具函數都被放在了 utils 目錄下。緩存
actionTypes.js
這個工具模塊定義了幾種 redux 預留的 action type,包括 reducer 替換類型、reducer 初始化類型和隨機類型。下面上源碼:
// 定義了一些 redux 保留的 action type。
// 隨機字符串確保惟一性。
const randomString = () =>
Math.random()
.toString(36)
.substring(7)
.split('')
.join('.')
const ActionTypes = {
INIT: `@@redux/INIT${randomString()}`,
REPLACE: `@@redux/REPLACE${randomString()}`,
PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
export default ActionTypes
複製代碼
能夠看出就是返回了一個 ActionTypes 對象,裏面包含三種類型:INIT、REPLACE 和 PROBE_UNKNOW_ACTION。分別對應以前所說的幾種類型,爲了防止和用戶自定義的 action type 相沖突,刻意在 type 裏面加入了隨機值。在後面的使用中,經過引入 ActionType 對象來進行對比。
isPlainObject.js
這個函數用於判斷傳入的對象是不是純對象,由於 redux 要求 action 和 state 是一個純對象,因此這個函數誕生了。
上源碼:
/** * 判斷一個參數是不是純對象,純對象的定義就是它的構造函數爲 Object。 * 好比: { name: 'isPlainObject', type: 'funciton' }。 * 而 isPlainObject 這個函數就不是純對象,由於它的構造函數是 Function。 * @param {any} obj 要檢查的對象。 * @returns {boolean} 返回的檢查結果,true 表明是純對象。 */
export default function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
// 獲取最頂級的原型,若是就是自身,那麼說明是純對象。
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
複製代碼
warning.js
這個函數用於拋出適當的警告,沒啥好說的。
/** * Prints a warning in the console if it exists. * * @param {String} message The warning message. * @returns {void} */
export default function warning(message) {
/* eslint-disable no-console */
if (typeof console !== 'undefined' && typeof console.error === 'function') {
console.error(message)
}
/* eslint-enable no-console */
try {
// This error was thrown as a convenience so that if you enable
// "break on all exceptions" in your console,
// it would pause the execution at this line.
throw new Error(message)
} catch (e) {} // eslint-disable-line no-empty
}
複製代碼
compose.js
這個函數用於嵌套調用中間件(middleware),進行初始化。
/** * 傳入一系列的單參數函數做爲參數(funcs 數組),返回一個新的函數,這個函數能夠接受 * 多個參數,運行時會將 funcs 數組中的函數從右至左進行調用。 * @param {...Function} funcs 一系列中間件。 * @returns {Function} 返回的結果函數。 * 從右至左調用,好比: compose(f, g, h) 將會返回一個新函數 * (...args) => f(g(h(...args))). */
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
// 經過 reduce 方法迭代。
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製代碼
看完了工具函數和入口函數,接下來就要正式步入主題了。咱們使用 redux 的重要一步就是經過 createStore 方法建立 store。那麼接下來看看這個方法是怎麼建立 store 的,store 又是個什麼東西呢?
咱們看源碼:
import $$observable from 'symbol-observable'
// 後面會講。
import ActionTypes from './utils/actionTypes'
// 引入一些預約義的保留的 action type。
import isPlainObject from './utils/isPlainObject'
// 判斷一個對象是不是純對象。
// 使用 redux 最主要的 API,就是這個 createStore,它用於建立一個 redux store,爲你提供狀態管理。
// 它接受三個參數(第二三個可選),第一個是 reducer,用於改變 redux store 的狀態;第二個是初始化的 store,
// 即最開始時候 store 的快照;第三個參數是由 applyMiddleware 函數返回的 enhancer 對象,使用中間件必須
// 提供的參數。
export default function createStore(reducer, preloadedState, enhancer) {
// 下面這一段基本能夠不看,它們是對參數進行適配的。
/*************************************參數適配****************************************/
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
// 若是傳遞了多個 enhancer,拋出錯誤。
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function'
)
}
// 若是沒有傳遞默認的 state(preloadedState 爲函數類型,enhancer 爲未定義類型),那麼傳遞的
// preloadedState 即爲 enhancer。
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
// 若是 enhancer 爲不爲空且非函數類型,報錯。
throw new Error('Expected the enhancer to be a function.')
}
// 使用 enhancer 對 createStore 進行處理,引入中間件。注意此處沒有再傳遞 enhancer 做爲參數。實際上 enhancer 會對 createStore 進行處理,而後返回一個實際意義上的 createStore 用於建立 store 對象,參考 applyMiddleware.js。
return enhancer(createStore)(reducer, preloadedState)
}
// 若是 reducer 不是函數類型,報錯。
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
/*********************************************************************************/
// 在函數內部定義一系列局部變量,用於存儲數據。
let currentReducer = reducer // 存儲當前的 reducer。
let currentState = preloadedState // 用於存儲當前的 store,即爲 state。
let currentListeners = [] // 用於存儲經過 store.subscribe 註冊的當前的全部訂閱者。
let nextListeners = currentListeners // 新的 listeners 數組,確保不直接修改 listeners。
let isDispatching = false // 當前狀態,防止 reducer 嵌套調用。
// 顧名思義,確保 nextListeners 能夠被修改,當 nextListeners 與 currentListeners 指向同一個數組的時候
// 讓 nextListeners 成爲 currentListeners 的副本。防止修改 nextListeners 致使 currentListeners 發生變化。
// 一開始我也不是很明白爲何會存在 nextListeners,由於後面 dispatch 函數中仍是直接把 nextListeners 賦值給了 currentListeners。
// 直接使用 currentListeners 也是能夠的。後來去 redux 的 repo 搜了搜,發現了一個 issue(https://github.com/reduxjs/redux/issues/2157) 講述了這個作法的理由。
// 提交這段代碼的做者的解釋(https://github.com/reduxjs/redux/commit/c031c0a8d900e0e95a4915ecc0f96c6fe2d6e92b)是防止 Array.slice 的濫用,只有在必要的時候調用 Array.slice 方法來複制 listeners。
// 之前的作法是每次 dispatch 都要 slice 一次,致使了性能的下降吧。
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// 返回 currentState,即 store 的快照。
function getState() {
// 防止在 reducer 中調用該方法,reducer 會接受 state 參數。
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
// store.subscribe 函數,訂閱 dispatch。
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 不容許在 reducer 中進行訂閱。
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
let isSubscribed = true
// 每次操做 nextListeners 以前先確保能夠修改。
ensureCanMutateNextListeners()
// 存儲訂閱者的註冊方法。
nextListeners.push(listener)
// 返回一個用於註銷當前訂閱者的函數。
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
isSubscribed = false
// 每次操做 nextListeners 以前先確保能夠修改。
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
// store.dispatch 函數,用於觸發 reducer 修改 state。
function dispatch(action) {
if (!isPlainObject(action)) {
// action 必須是純對象。
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
// 每一個 action 必須包含一個 type 屬性,指定修改的類型。
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// reducer 內部不容許派發 action。
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
// 調用 reducer 以前,先將標誌位置一。
isDispatching = true
// 調用 reducer,返回的值即爲最新的 state。
currentState = currentReducer(currentState, action)
} finally {
// 調用完以後將標誌位置 0,表示 dispatch 結束。
isDispatching = false
}
// dispatch 結束以後,執行全部訂閱者的函數。
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
// 返回當前所使用的 action,這一步是中間件嵌套使用的關鍵,很重要。
return action
}
// 一個比較新的 API,用於動態替換當前的 reducers。適用於按需加載,代碼拆分等場景。
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
// 執行默認的 REPLACE 類型的 action。在 combineReducers 函數中有使用到這個類型。
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
// 這是爲了適配 ECMA TC39 會議的一個有關 Observable 的提案(參考https://github.com/tc39/proposal-observable)所寫的一個函數。
// 做用是訂閱 store 的變化,適用於全部實現了 Observable 的類庫(主要是適配 RxJS)。
// 我找到了引入這個功能的那個 commit:https://github.com/reduxjs/redux/pull/1632。
function observable() {
// outerSubscribe 即爲外部的 subscribe 函數。
const outerSubscribe = subscribe
// 返回一個純對象,包含 subscribe 方法。
return {
subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}
// 用於給 subscribe 註冊的函數,嚴格按照 Observable 的規範實現,observer 必須有一個 next 屬性。
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
// $$observable 即爲 Symbol.observable,也屬於 Observable 的規範,返回自身。
[$$observable]() {
return this
}
}
}
// 初始化時 dispatch 一個 INIT 類型的 action,校驗各類狀況。
dispatch({ type: ActionTypes.INIT })
// 返回一個 store 對象。
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
複製代碼
不難發現,咱們的 store 對象就是一個純 JavaScript 對象。包含幾個屬性 API,而咱們的 state 就存儲在 createStore 這個方法內部,是一個局部變量,只能經過 getState 方法訪問到。這裏其實是對閉包的利用,全部咱們操做的 state 都存儲在 getState 方法內部的一個變量裏面。直到咱們的程序結束(或者說 store 被銷燬),createStore 方法纔會被回收,裏面的變量纔會被銷燬。
而 subscribe 方法就是對觀察者模式的利用(注意不是發佈訂閱模式,兩者有區別,不要混淆),咱們經過 subscribe 方法註冊咱們的函數,咱們的函數會給存儲到 createStore 方法的一個局部變量當中,每次 dispatch 被調用以後,都會遍歷一遍 currentListeners,依次執行其中的方法,達到咱們訂閱的要求。
瞭解了 createStore 究竟是怎麼一回事,咱們再來看看 combineReducers 究竟是怎麼建立 reducer 的。
咱們寫 reducer 的時候,其實是在寫一系列函數,而後整個到一個對象的屬性上,最後傳給 combineReducers 進行處理,處理以後就能夠供 createStore 使用了。
例如:
// 建立咱們的 reducers。
const _reducers = {
items(items = [], { type, payload }) {
if (type === 'ADD_ITEMS') items.push(payload);
return items;
},
isLoading(isLoading = false, { type, payload }) {
if (type === 'IS_LOADING') return true;
return false;
}
};
// 交給 combineReducers 處理,適配 createStore。
const reducers = combineReducers(_reducers);
// createStore 接受 reducers,建立咱們須要的 store。
const store = createStore(reducers);
複製代碼
那麼 combineReducers 對咱們的 reducers 對象進行了哪些處理呢?
下面的代碼比較長,但願你們能有耐心。
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
/** * 用於獲取錯誤信息的工具函數,若是調用你所定義的某個 reducer 返回了 undefined,那麼就調用這個函數 * 拋出合適的錯誤信息。 * * @param {String} key 你所定義的某個 reducer 的函數名,同時也是 state 的一個屬性名。 * * @param {Object} action 調用 reducer 時所使用的 action。 */
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionDescription =
(actionType && `action "${String(actionType)}"`) || 'an action'
return (
`Given ${actionDescription}, reducer "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
}
/** * 工具函數,用於校驗未知鍵,若是 state 中的某個屬性沒有對應的 reducer,那麼返回報錯信息。 * 對於 REPLACE 類型的 action type,則不進行校驗。 * @param {Object} inputState * @param {Object} reducers * @param {Object} action * @param {Object} unexpectedKeyCache */
function getUnexpectedStateShapeWarningMessage( inputState, reducers, action, unexpectedKeyCache ) {
const reducerKeys = Object.keys(reducers)
const argumentName =
action && action.type === ActionTypes.INIT
? 'preloadedState argument passed to createStore'
: 'previous state received by the reducer'
// 若是 reducers 長度爲 0,返回對應錯誤信息。
if (reducerKeys.length === 0) {
return (
'Store does not have a valid reducer. Make sure the argument passed ' +
'to combineReducers is an object whose values are reducers.'
)
}
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` +
{}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + // {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1]
`". Expected argument to be an object with the following ` + // 返回的是 inputState 的類型。
`keys: "${reducerKeys.join('", "')}"`
)
}
// 獲取全部 State 有而 reducers 沒有的屬性,加入到 unexpectedKeysCache。
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
// 加入到 unexpectedKeyCache。
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
// 若是是 REPLACE 類型的 action type,再也不校驗未知鍵,由於按需加載的 reducers 不須要校驗未知鍵,如今不存在的 reducers 可能下次就加上了。
if (action && action.type === ActionTypes.REPLACE) return
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
/** * 用於校驗全部 reducer 的合理性:傳入任意值都不能返回 undefined。 * @param {Object} reducers 你所定義的 reducers 對象。 */
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
// 獲取初始化時的 state。
const initialState = reducer(undefined, { type: ActionTypes.INIT })
if (typeof initialState === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`
)
}
// 若是初始化校驗經過了,有多是你定義了 ActionTypes.INIT 的操做。這時候從新用隨機值校驗。
// 若是返回 undefined,說明用戶可能對 INIT type 作了對應處理,這是不容許的。
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined'
) {
throw new Error(
`Reducer "${key}" returned undefined when probed with a random type. ` +
`Don't try to handle ${ ActionTypes.INIT } or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined, but can be null.`
)
}
})
}
// 把你所定義的 reducers 對象轉化爲一個龐大的彙總函數。
// 能夠看出,combineReducers 接受一個 reducers 對象做爲參數,
// 而後返回一個總的函數,做爲最終的合法的 reducer,這個 reducer
// 接受 action 做爲參數,根據 action 的類型遍歷調用全部的 reducer。
export default function combineReducers(reducers) {
// 獲取 reducers 全部的屬性名。
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
// 遍歷 reducers 的全部屬性,剔除全部不合法的 reducer。
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
// 將 reducers 中的全部 reducer 拷貝到新的 finalReducers 對象上。
finalReducers[key] = reducers[key]
}
}
// finalReducers 是一個純淨的通過過濾的 reducers 了,從新獲取全部屬性名。
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
// unexpectedKeyCache 包含全部 state 中有可是 reducers 中沒有的屬性。
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
// 校驗全部 reducer 的合理性,緩存錯誤。
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// 這就是返回的新的 reducer,一個純函數。每次 dispatch 一個 action,都要執行一遍 combination 函數,
// 進而把你所定義的全部 reducer 都執行一遍。
return function combination(state = {}, action) {
if (shapeAssertionError) {
// 若是有緩存的錯誤,拋出。
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
// 非生產環境下校驗 state 中的屬性是否都有對應的 reducer。
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
// state 是否改變的標誌位。
const nextState = {}
// reducer 返回的新 state。
for (let i = 0; i < finalReducerKeys.length; i++) { // 遍歷全部的 reducer。
const key = finalReducerKeys[i] // 獲取 reducer 名稱。
const reducer = finalReducers[key] // 獲取 reducer。
const previousStateForKey = state[key] // 舊的 state 值。
const nextStateForKey = reducer(previousStateForKey, action) // 執行 reducer 返回的新的 state[key] 值。
if (typeof nextStateForKey === 'undefined') {
// 若是通過了那麼多校驗,你的 reducer 仍是返回了 undefined,那麼就要拋出錯誤信息了。
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
// 把返回的新值添加到 nextState 對象上,這裏能夠看出來,你所定義的 reducer 的名稱就是對應的 state 的屬性,因此 reducer 命名要規範!
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
// 檢驗 state 是否發生了變化。
}
// 根據標誌位返回對應的 state。
return hasChanged ? nextState : state
}
}
複製代碼
combineReducers 方法代碼比較多,可是實際邏輯仍是很簡單的,接下來這個函數代碼很少,可是邏輯要稍微複雜一點,它就是應用中間件的 applyMiddleware 函數。這個函數的思想比較巧妙,值得學習。
import compose from './compose'
// 用於應用中間件的函數,能夠同時傳遞多箇中間件。中間件的標準形式爲:
// const middleware = store => next => action => { /*.....*/ return next(action); }
export default function applyMiddleware(...middlewares) {
// 返回一個函數,接受 createStore 做爲參數。args 參數即爲 reducer 和 preloadedState。
return createStore => (...args) => {
// 在函數內部調用 createStore 建立一個 store 對象,這裏不會傳遞 enhancer,由於 applyMiddleware 自己就是在建立一個 enhancer,而後給 createStore 調用。
// 這裏其實是經過 applyMiddleware 把 store 的建立推遲了。爲何要推遲呢?由於要利用 middleWares 作文章,先初始化中間件,從新定義 dispatch,而後再建立 store,這時候建立的 store 所包含的 dispatch 方法就區別於不傳遞 enhancer 時所建立的 dispatch 方法了,其中包含了中間件所定義的一些邏輯,這就是爲何中間件能夠干預 dispatch 的緣由。
const store = createStore(...args)
// 這裏對 dispatch 進行了從新定義,無論傳入什麼參數,都會報錯,這樣作的目的是防止你的中間件在初始化的時候就
// 調用 dispatch。
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args) // 注意最後 dispatch 的時候不會訪問上面報錯的那個 dispatch 函數了,由於那個函數被下面的 dispatch 覆蓋了。
}
// 對於每個 middleware,都傳入 middlewareAPI 進行調用,這就是中間件的初始化。
// 初始化後的中間件返回一個新的函數,這個函數接受 store.dispatch 做爲參數,返回一個替換後的 dispatch,做爲新的
// store.dispatch。
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// compose 方法把全部中間件串聯起來調用。用最終結果替換 dispatch 函數,以後所使用的全部 store.dispatch 方法都已是
// 替換了的,加入了新的邏輯。
dispatch = compose(...chain)(store.dispatch)
// 初始化中間件之後,把報錯的 dispatch 函數覆蓋掉。
/** * middle 的標準形式: * const middleware = ({ getState, dispatch }) => next => action => { * // .... * return next(action); * } * 這裏 next 是通過上一個 middleware 處理了的 dispatch 方法。 * next(action) 返回的仍然是一個 dispatch 方法。 */
return {
...store,
dispatch // 全新的 dispatch。
}
}
}
複製代碼
代碼量真的不多,可是真的很巧妙,這裏有幾點很關鍵:
compose 方法利用 Array.prototype.reduce
實現中間件的嵌套調用,返回一個全新的函數,能夠接受新的參數(上一個中間件處理過的 dispatch),最終返回一個全新的包含新邏輯的 dispatch 方法。你看 middleware 通過初始化後返回的函數的格式:
next => action => {
return next(action);
}
複製代碼
其中 next
能夠當作 dispatch
,這不就是接受一個 dispatch 做爲參數,而後返回一個新的 dispatch 方法嗎?緣由就是咱們能夠認爲接受 action 做爲參數,而後觸發 reducer 更改 state 的全部函數都是 dispatch 函數。
middleware 中間件通過初始化之後,返回一個新函數,它接受 dispatch 做爲參數,而後返回一個新的 dispatch 又能夠供下一個 middleware 調用,這就致使全部的 middleware 能夠嵌套調用了!並且最終返回的結果也是一個 dispatch 函數。
最終獲得的 dispatch 方法,是把原始的 store.dispatch 方法傳遞給最後一個 middleware,而後層層嵌套處理,最後通過第一個 middleware 處理過之後所返回的方法。因此咱們在調用應用了中間件的 dispatch 函數時,從左至右的通過了 applyMiddleware 方法的全部參數(middleware)的處理。這有點像是包裹和拆包裹的過程。
爲何 action 能夠通過全部的中間件處理呢?咱們再來看看中間件的基本結構:
({ dispatch, getState }) => next => action => {
return next(action);
}
複製代碼
咱們能夠看到 action 進入函數之後,會通過 next 的處理,而且會返回結果。next 會返回什麼呢?由於第一個 next 的值就是 store.dispatch,因此看看 store.dispatch 的源碼就知道了。
function dispatch(action) {
// 省略了一系列操做的代碼……
// 返回當前所使用的 action,這一步是中間件嵌套使用的關鍵。
return action
}
複製代碼
沒錯,store.dispatch 最終返回了 action,因爲中間件嵌套調用,因此每一個 next 都返回 action,而後又能夠供下一個 next 使用,環環相扣,十分巧妙。
這部分描述的有點拗口,語言捉急但又不想畫圖,各位仍是本身多想一想好了。
這個方法沒有太多好說的,主要做用是減小你們 dispatch reducer 所要寫的代碼,好比你原來有一個 action:
const addItems = item => ({
type: 'ADD_ITEMS',
payload: item
});
複製代碼
而後你要調用它的時候:
store.dispatch(addItems('item value'));
複製代碼
若是你使用 bindActionCreators:
const _addItems = bindActionCreators(addItems, store.dispatch);
複製代碼
當你要 dispatch reducer 的時候:
_addItems('item value');
複製代碼
這樣就減小了你要寫的重複代碼,另外你還能夠把全部的 action 寫在一個對象裏面傳遞給 bindActionCreators,就像傳遞給 combineReducers 的對象那樣。
下面看看源碼:
/** * 該函數返回一個新的函數,調用新的函數會直接 dispatch ActionCreator 所返回的 action。 * 這個函數是 bindActionCreators 函數的基礎,在 bindActionCreators 函數中會把 actionCreators 拆分紅一個一個 * 的 ActionCreator,而後調用 bindActionCreator 方法。 * @param {Function} actionCreator 一個返回 action 純對象的函數。 * @param {Function} dispatch store.dispatch 方法,用於觸發 reducer。 */
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
// 接受一個 actionCreator(或者一個 actionCreators 對象)和一個 dispatch 函數做爲參數,
// 而後返回一個函數或者一個對象,直接執行這個函數或對象中的函數可讓你沒必要再調用 dispatch。
export default function bindActionCreators(actionCreators, dispatch) {
// 若是 actionCreators 是一個函數而非對象,那麼直接調用 bindActionCreators 方法進行轉換,此時返回
// 結果也是一個函數,執行這個函數會直接 dispatch 對應的 action。
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// actionCreators 既不是函數也不是對象,或者爲空時,拋出錯誤。
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${ actionCreators === null ? 'null' : typeof actionCreators }. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
// 若是 actionCreators 是一個對象,那麼它的每個屬性就應該是一個 actionCreator,遍歷每個 actionCreator,
// 使用 bindActionCreator 進行轉換。
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
// 把轉換結果綁定到 boundActionCreators 對象,最後會返回它。
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
複製代碼
這部分挺簡單的,主要做用在於把 action creator 轉化爲能夠直接使用的函數。
看了源碼之後,以爲中間件並無想象中的那麼晦澀難懂了。就是一個基本的格式,而後你在你的中間件裏面能夠隨心所欲,最後調用固定的方法,返回固定的內容就完事了。這就是爲何大多數 redux middleware 的源碼都很短小精悍的緣由。
看看 redux-thunk 的源碼:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
複製代碼
是否是很短很小?那麼到底幹了什麼讓它這麼受歡迎呢?
實際上 redux-thunk 能夠被認爲就是:
// 這就是典型的 middleware 格式。
({ dispatch, getState }) => next => action => {
// next 就是 dispatch 方法。註釋所在的函數就是返回的新的 dispatch。
// 先判斷一下 action 是否是一個函數。
if (typeof action === 'function') {
// 若是是函數,調用它,傳遞 dispatch,getState 和 多餘的參數做爲 aciton 的參數。
return action(dispatch, getState, extraArgument);
}
// 若是 action 不是函數,直接 nextr調用 action,返回結果就完事兒了。
return next(action);
};
複製代碼
怎麼樣,是否是很簡單?它乾的事就是判斷了一下 action 的類型,若是是函數就調用,不是函數就用 dispatch 來調用,很簡單。
可是它實現的功能很實用,容許咱們傳遞函數做爲 store.dispatch 的參數,這個函數的參數應該是固定的,必須符合上面代碼的要求,接受 dispatch、getState做爲參數,而後這個函數應該返回實際的 action。
咱們也能夠寫一個本身的中間件了:
({ dispatch, getState }) => next => action => {
return action.then ? action.then(next) : next(action);
}
複製代碼
這個中間件容許咱們傳遞一個 Promise 對象做爲 action,而後會等 action 返回結果(一個真正的 action)以後,再進行 dispatch。
固然因爲 action.then() 返回的不是實際上的 action(一個純對象),因此這個中間件可能無法跟其餘中間件一塊兒使用,否則其餘中間件接受不到 action 會出問題。這只是個示例,用於說明中間件沒那麼複雜,可是咱們能夠利用中間件作不少事情。
若是想要了解更加複雜的 redux 中間件,能夠參考:
最後,時間緊迫,水平有限,不免存在紕漏或錯誤,請你們多多包涵、多多指教、共同進步。