在進(jìn)入正題前,我們首先來看一下在項(xiàng)目中是如何使用 Redux 的掰担,根據(jù)使用步驟來講解源碼怒炸。
// 首先把多個 reducer 通過 combineReducers 組合在一起
const appReducer = combineReducers({
user: UserReducer,
goods: GoodsReducer,
order: OrdersReducer,
chat: ChatReducer
});
// 然后將 appReducer 傳入 createStore阅羹,并且通過 applyMiddleware 使用了中間件 thunkMiddleware
// 然后在需要的地方發(fā)起 dispatch(action) 引起 state 改變
export default function configureStore() {
const store = createStore(
rootReducer,
compose(
applyMiddleware(thunkMiddleware),
window.devToolsExtension ? window.devToolsExtension() : f => f
)
);
return store;
}
介紹完了使用步驟捏鱼,接下來進(jìn)入正題。
源碼解析
首先讓我們來看下 combineReducers
函數(shù)
// 傳入一個 object
export default function combineReducers(reducers) {
// 獲取該 Object 的 key 值
const reducerKeys = Object.keys(reducers)
// 過濾后的 reducers
const finalReducers = {}
// 獲取每一個 key 對應(yīng)的 value
// 在開發(fā)環(huán)境下判斷值是否為 undefined
// 然后將值類型是函數(shù)的值放入 finalReducers
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') {
finalReducers[key] = reducers[key]
}
}
// 拿到過濾后的 reducers 的 key 值
const finalReducerKeys = Object.keys(finalReducers)
// 在開發(fā)環(huán)境下判斷,保存不期望 key 的緩存用以下面做警告
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
// 該函數(shù)解析在下面
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// combineReducers 函數(shù)返回一個函數(shù)猿诸,也就是合并后的 reducer 函數(shù)
// 該函數(shù)返回總的 state
// 并且你也可以發(fā)現(xiàn)這里使用了閉包狡忙,函數(shù)里面使用到了外面的一些屬性
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
// 該函數(shù)解析在下面
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
// state 是否改變
let hasChanged = false
// 改變后的 state
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
// 拿到相應(yīng)的 key
const key = finalReducerKeys[i]
// 獲得 key 對應(yīng)的 reducer 函數(shù)
const reducer = finalReducers[key]
// state 樹下的 key 是與 finalReducers 下的 key 相同的
// 所以你在 combineReducers 中傳入的參數(shù)的 key 即代表了 各個 reducer 也代表了各個 state
const previousStateForKey = state[key]
// 然后執(zhí)行 reducer 函數(shù)獲得該 key 值對應(yīng)的 state
const nextStateForKey = reducer(previousStateForKey, action)
// 判斷 state 的值,undefined 的話就報錯
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 然后將 value 塞進(jìn)去
nextState[key] = nextStateForKey
// 如果 state 改變
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// state 只要改變過北专,就返回新的 state
return hasChanged ? nextState : state
}
}
combineReducers
函數(shù)總的來說很簡單旬陡,總結(jié)來說就是接收一個對象描孟,將參數(shù)過濾后返回一個函數(shù)。該函數(shù)里有一個過濾參數(shù)后的對象 finalReducers场航,遍歷該對象溉痢,然后執(zhí)行對象中的每一個 reducer 函數(shù),最后將新的 state 返回。
接下來讓我們來看看 combinrReducers 中用到的兩個函數(shù)
// 這是執(zhí)行的第一個用于拋錯的函數(shù)
function assertReducerShape(reducers) {
// 將 combineReducers 中的參數(shù)遍歷
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
// 給他傳入一個 action
const initialState = reducer(undefined, { type: ActionTypes.INIT })
// 如果得到的 state 為 undefined 就拋錯
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.`
)
}
// 再過濾一次汽畴,考慮到萬一你在 reducer 中給 ActionTypes.INIT 返回了值
// 傳入一個隨機(jī)的 action 判斷值是否為 undefined
const type =
'@@redux/PROBE_UNKNOWN_ACTION_' +
Math.random()
.toString(36)
.substring(7)
.split('')
.join('.')
if (typeof reducer(undefined, { type }) === '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.`
)
}
})
}
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
// 這里的 reducers 已經(jīng)是 finalReducers
const reducerKeys = Object.keys(reducers)
const argumentName =
action && action.type === ActionTypes.INIT
? 'preloadedState argument passed to createStore'
: 'previous state received by the reducer'
// 如果 finalReducers 為空
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.'
)
}
// 如果你傳入的 state 不是對象
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` +
{}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
}
// 將參入的 state 于 finalReducers 下的 key 做比較,過濾出多余的 key
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
if (action && action.type === ActionTypes.REPLACE) return
// 如果 unexpectedKeys 有值的話
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.`
)
}
}
接下來讓我們先來看看 compose
函數(shù)
// 這個函數(shù)設(shè)計(jì)的很巧妙,通過傳入函數(shù)引用的方式讓我們完成多個函數(shù)的嵌套使用隙券,術(shù)語叫做高階函數(shù)
// 通過使用 reduce 函數(shù)做到從右至左調(diào)用函數(shù)
// 對于上面項(xiàng)目中的例子
compose(
applyMiddleware(thunkMiddleware),
window.devToolsExtension ? window.devToolsExtension() : f => f
)
// 經(jīng)過 compose 函數(shù)變成了 applyMiddleware(thunkMiddleware)(window.devToolsExtension()())
// 所以在找不到 window.devToolsExtension 時你應(yīng)該返回一個函數(shù)
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
然后我們來解析 createStore
函數(shù)的部分代碼
export default function createStore(reducer, preloadedState, enhancer) {
// 第4個參數(shù)也是函數(shù)時,報錯牲迫,多個增強(qiáng)函數(shù)可以合并成一個
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
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.'
)
}
// 一般 preloadedState 用的少铐刘,判斷類型檩禾,如果第二個參數(shù)是函數(shù)且沒有第三個參數(shù)画株,就調(diào)換位置
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// 判斷 enhancer 是否是函數(shù)
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 類型沒錯的話芹关,先執(zhí)行 enhancer侥衬,然后再執(zhí)行 createStore 函數(shù)
return enhancer(createStore)(reducer, preloadedState)
}
// 判斷 reducer 是否是函數(shù)
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
// 當(dāng)前 reducer
let currentReducer = reducer
// 當(dāng)前狀態(tài)
let currentState = preloadedState
// 當(dāng)前監(jiān)聽函數(shù)數(shù)組
let currentListeners = []
// 這是一個很重要的設(shè)計(jì)博个,為的就是每次在遍歷監(jiān)聽器的時候保證 currentListeners 數(shù)組不變
// 可以考慮下只存在 currentListeners 的情況往堡,如果我在某個 subscribe 中再次執(zhí)行 subscribe
// 或者 unsubscribe,這樣會導(dǎo)致當(dāng)前的 currentListeners 數(shù)組大小發(fā)生改變穆咐,從而可能導(dǎo)致
// 索引出錯
let nextListeners = currentListeners
// reducer 是否正在執(zhí)行
let isDispatching = false
/**
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
*/
// 如果 currentListeners 和 nextListeners 相同庸娱,就賦值回去
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
// 然后在 createStore 末尾會發(fā)起一個 action dispatch({ type: ActionTypes.INIT });
// 用以初始化 state
dispatch({ type: ActionTypes.INIT })
//dispatch一個用于初始化的action,相當(dāng)于調(diào)用一次reducer
//然后將reducer中的子reducer的初始值也獲取到
//詳見下面reducer的實(shí)現(xiàn)。
return {
dispatch,
subscribe,
getState,
//下面兩個是主要面向庫開發(fā)者的方法一铅,暫時先忽略
replaceReducer,
[$$observable]: observable
}
}
可以看出,createStore方法創(chuàng)建了一個store,但是并沒有直接將這個store的狀態(tài)state返回,而是返回了一系列方法柑土,外部可以通過這些方法(getState)獲取state灸促,或者間接地(通過調(diào)用dispatch)改變state荒叼。
至于state呢萝玷,被存在了閉包中蜓斧。
我們再來詳細(xì)的看看每個模塊是如何實(shí)現(xiàn)的
getState
// 獲取state
function getState() {
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
}
dispatch
// 觸發(fā)了一個action豆拨,因此我們調(diào)用reducer,得到的新的state邮绿,并且執(zhí)行所有添加到store中的監(jiān)聽函數(shù)。
function dispatch(action) {
// 原生的 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?'
)
}
// 注意在 Reducers 中是不能執(zhí)行 dispatch 函數(shù)的
// 因?yàn)槟阋坏┰?reducer 函數(shù)中執(zhí)行 dispatch,會引發(fā)死循環(huán)
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
// 執(zhí)行 combineReducers 組合后的函數(shù)
try {
isDispatching = true
//調(diào)用reducer凛辣,得到新state
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 然后遍歷 currentListeners,執(zhí)行數(shù)組中保存的函數(shù)
const listeners = (currentListeners = nextListeners)
//調(diào)用監(jiān)聽數(shù)組中的所有監(jiān)聽函數(shù)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
subscribe
subscribe返回的是一個取消訂閱的方法。取消訂閱是非常必要的锁右,當(dāng)添加的監(jiān)聽器沒用了之后痪署,應(yīng)該從store中清理掉余寥。不然每次dispatch都會調(diào)用這個沒用的監(jiān)聽器呐馆。
// 添加一個監(jiān)聽函數(shù)续膳,每當(dāng)dispatch被調(diào)用的時候都會執(zhí)行這個監(jiān)聽函數(shù)
function subscribe(listener) {
// 添加到監(jiān)聽函數(shù)數(shù)組摔桦,
// 注意:我們添加到了下一次dispatch時才會生效的數(shù)組
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
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.'
)
}
//設(shè)置一個標(biāo)志,標(biāo)志該監(jiān)聽器已經(jīng)訂閱了
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
//subscribe返回的是一個取消訂閱的方法。
//取消訂閱是非常必要的鸥拧,當(dāng)添加的監(jiān)聽器沒用了之后钠绍,應(yīng)該從store中清理掉。
//不然每次dispatch都會調(diào)用這個沒用的監(jiān)聽器赵誓。
return function unsubscribe() {
if (!isSubscribed) {
return // 如果已經(jīng)取消訂閱過了幻枉,直接返回
}
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
ensureCanMutateNextListeners()
// 從下一輪的監(jiān)聽函數(shù)數(shù)組(用于下一次dispatch)中刪除這個監(jiān)聽器蔓罚。
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
在這之前我需要先來介紹一下函數(shù)柯里化贡这,柯里化是一種將使用多個參數(shù)的一個函數(shù)轉(zhuǎn)換成一系列使用一個參數(shù)的函數(shù)的技術(shù)。
function add(a,b) { return a + b }
add(1, 2) => 3
// 對于以上函數(shù)如果使用柯里化可以這樣改造
function add(a) {
return b => {
return a + b
}
}
add(1)(2) => 3
// 你可以這樣理解函數(shù)柯里化正歼,通過閉包保存了外部的一個變量,然后返回一個接收參數(shù)的函數(shù),在該函數(shù)中使用了保存的變量湃密,然后再返回值。
在redux中使用中間件
還記得redux 的createStore()方法的第三個參數(shù)enhancer嗎?
function createStore(reducer, preloadedState, enhancer) {
if(enhancer是有效的){
return enhancer(createStore)(reducer, preloadedState)
}
//...
}
在這里铺厨,我們可以看到赃磨,enhancer(可以叫做強(qiáng)化器)是一個函數(shù),這個函數(shù)接受一個「普通createStore函數(shù)」作為參數(shù)缕减,返回一個「加強(qiáng)后的createStore函數(shù)」搅裙。
applyMiddleware嫂易,顧名思義颅和,「應(yīng)用中間件」。輸入為若干中間件,輸出為enhancer案训。面來看看它的源碼:
// 這個函數(shù)應(yīng)該是整個源碼中最難理解的一塊了
// 該函數(shù)返回一個柯里化的函數(shù)
// 所以調(diào)用這個函數(shù)應(yīng)該這樣寫 applyMiddleware(...middlewares)(createStore)(...args)
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
//用參數(shù)傳進(jìn)來的createStore創(chuàng)建一個store
const store = createStore(...args)
//注意猜旬,我們在這里需要改造的只是store的dispatch方法
let dispatch = () => {
//一個臨時的dispatch
//作用是在dispatch改造完成前調(diào)用dispatch只會打印錯誤信息
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
// 每個中間件都應(yīng)該有這兩個函數(shù)
//接下來我們準(zhǔn)備將每個中間件與我們的state關(guān)聯(lián)起來(通過傳入getState方法)熟嫩,得到改造函數(shù)柠逞。
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 把 middlewares 中的每個中間件都傳入 middlewareAPI
//middlewares是一個中間件函數(shù)數(shù)組,中間件函數(shù)的返回值是一個改造dispatch的函數(shù)
//調(diào)用數(shù)組中的每個中間件函數(shù),得到所有的改造函數(shù)
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 和之前一樣,從右至左調(diào)用每個中間件,然后傳入 store.dispatch
//將這些改造函數(shù)compose(翻譯:構(gòu)成,整理成)成一個函數(shù)
//用compose后的函數(shù)去改造store的dispatch
dispatch = compose(...chain)(store.dispatch)
// compose方法的作用是,例如這樣調(diào)用:
// compose(func1,func2,func3)
// 返回一個函數(shù): (...args) => func1( func2( func3(...args) ) )
// 即傳入的dispatch被func3改造后得到一個新的dispatch糠悯,新的dispatch繼續(xù)被func2改造...
// 返回store讯泣,用改造后的dispatch方法替換store中的dispatch
return {
...store,
dispatch
}
}
}
總結(jié)一下好渠,applyMiddleware的工作方式是:
調(diào)用(若干個)中間件函數(shù)拳锚,獲然舨簟(若干個)改造函數(shù)
把所有改造函數(shù)compose成一個改造函數(shù)
改造dispatch方法
中間件的工作方式是:
中間件是一個函數(shù),不妨叫做中間件函數(shù)
中間件函數(shù)的輸入是store的getState和dispatch简卧,輸出為改造函數(shù)(改造dispatch的函數(shù))
改造函數(shù)輸入是一個dispatch贞滨,輸出「改造后的dispatch」