從源代碼的入口文件發現,其實 redux 最終就只是導出了一個對象,對象中有幾個方法,代碼以下:html
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
複製代碼
因此重點分析幾個方法:react
方法中定義的一些變量:git
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
複製代碼
這些變量會被 dispatch
或者別的方法引用,從而造成閉包。這些變量不會被釋放。github
建立 srore
的方法最終返回的是一個對象。對象中含有比較重要的方法dispatch,subscribe,getState
。編程
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
複製代碼
其中 createStore
的第三個參數是應用中間件來作一些加強操做的。redux
if (typeof enhancer !== 'undefined') { // 若是加強方法存在就對 createStore 進行加強
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
複製代碼
其中 subscribe
用來註冊監聽方法,每一次註冊後會將監聽方法維護到數組currentListeners
中,currentListeners
是 createStore
中的一個變量,因爲被 subscribe
引用着因此造成了一個閉包。也就是經過閉包來維護狀態。數組
let currentListeners = []
複製代碼
dispatch
方法用來分發 action
, 函數裏面會生成新的 currentState
, 會執行全部註冊了的函數。閉包
核心代碼:app
try {
isDispatching = true
currentState = currentReducer(currentState, action) // 生成新的 state
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
} // 遍歷執行註冊函數
複製代碼
僅僅用來得到當前的 state
:異步
function getState() {
return currentState
}
複製代碼
函數中定義的一些變量,
const finalReducers = {}
const finalReducerKeys = Object.keys(finalReducers)
複製代碼
這個函數最後返回的是一個函數 combination
, 返回的函數中引用了 finalReducers
和 finalReducerKeys
,造成了閉包。
出於業務場景考慮,不一樣的模塊採用不一樣的 reducer
進行處理,因此 reducer
函數有不少。這些 reducer
會遍歷執行。
每一次 dispatch
一個 action
的時候就會執行
currentState = currentReducer(currentState, action) // 生成新的 state
複製代碼
這裏的 currentReducer
就是返回的 combination
函數。combination
函數中的核心代碼:
function combination(state = {}, action) {
...
let hasChanged = false
// 每一次 reducer 執行的時候都會生成一個新的對象來做爲新的 state
const nextState = {}
// 經過 for 循環遍歷 reducer
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
// 獲取當前的 state
const previousStateForKey = state[key]
// 執行相應的 reducer 後會生成新的 state
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 給新的 state 賦值
nextState[key] = nextStateForKey
// 若是是一個簡單類型好比 string,number
// 若是先後值同樣就不會觸發改變
// 但若是 state 中某個值是一個對象,
// 儘管先後對象中的值同樣,可是引用地址變化,仍是會觸發改變
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 因此若是簡單值沒有變化而且沒有對象的引用地址改變就會返回原來的 state
return hasChanged ? nextState : state
}
複製代碼
結合 react-redux
中向 redux
訂閱的方法發現
subscribe() {
const { store } = this.props // 這裏的 store 是 createStore 方法執行後返回的對象
this.unsubscribe = store.subscribe(() => { // 經過訂閱方法註冊監聽事件
const newStoreState = store.getState() // 獲取新的 state
if (!this._isMounted) {
return
}
// 經過使用函數替代對象傳入 setState 的方式可以獲得組件的 state 和 props 屬性可靠的值。
this.setState(providerState => {
// 若是值是同樣的就不會觸發更新
if (providerState.storeState === newStoreState) {
return null
}
return { storeState: newStoreState }
})
})
// Actions might have been dispatched between render and mount - handle those
const postMountStoreState = store.getState()
if (postMountStoreState !== this.state.storeState) {
this.setState({ storeState: postMountStoreState })
}
}
複製代碼
在註冊的 listen
方法中會發現若是最 新的state和原來的state同樣
就不會觸發 setState
方法的執行,從而就不會觸發 render
。
源碼:
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => { // 接收 createStore 函數做爲參數
const store = createStore(...args)
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)
} // 中間件函數接收的 API 參數,可以獲取到當前的 state 和 createStore 函數的參數
// 因此這裏就向中間件函數中傳遞了參數
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 經過函數組合生成一個新的 dispatch 函數
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
} // 這裏返回的是最後生成的 store,相比不使用中間件的區別是對 dispatch 進行了加強。
}
}
複製代碼
結合 createStore
中的源碼:
return enhancer(createStore)(reducer, preloadedState)
複製代碼
因此上面 applyMiddleware
中返回的函數就是這裏的 enhancer
方法,接收 createStore
做爲參數。
(reducer, preloadedState)
對應着中間件中的 (...args)
。
react-redux
經過提供 Provider
組件將 store
和整個應用中的組件聯繫起來。確保整個組件均可以得到 store
, 這是經過 Context
來實現的。
Provider
組件最終渲染的組件:
render() {
const Context = this.props.context || ReactReduxContext
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
複製代碼
其中 state
的定義以下:
const { store } = props
this.state = {
storeState: store.getState(),
store
}
複製代碼
因此 Provider
給應用提供 store
的寫法以下,屬性名必須是 store
。
<Provider store={store}>
<Router />
</Provider>
複製代碼
redux-thunk
是一箇中間件,直接看中間件的源代碼是絕對不可能看明白的
。
中間件不是一個完整的個體。它是爲了豐富或者擴展某個模塊而出現的,其中會調用一些原來的模塊的方法,因此若是不看源模塊的對應的方法實現,根本沒法理解。
因此要想看懂一箇中間件,必須結合源模塊的代碼一塊兒看。
JavaScript 語言是傳值調用,它的 Thunk 函數含義有所不一樣。在 JavaScript 語言中,Thunk 函數替換的不是表達式,而是多參數函數,將其替換成單參數的版本,且只接受回調函數做爲參數。
如何讓 dispatch 分發一個函數,也就是 action creator??
dispatch
的參數只能是一個普通的對象,若是要讓參數是一個函數,須要使用中間件 redux-thunk
。
設計思想就是一種面向切面編程AOP,對函數行爲的加強,也是裝飾模式的使用。
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;
複製代碼
若是隻是用了 thunk
,那麼最終加強版的 dispatch
就是
action => {
// 當 dispatch 參數是一個函數的時候執行這裏
if (typeof action === 'function') {
// 這裏的 dispatch 就是最原始的 dispatch
// 因此 action 函數中能夠直接使用參數 dispatch 和 getState 函數
return action(dispatch, getState, extraArgument);
}
return next(action); // 這裏的 next 是 store.dispatch
}
複製代碼
異步操做若是使用 action creator
, 則至少要送出兩個 Action:
action creator
函數中送出第二個 Action
代碼實例:
handleClick = () => {
const { dispatch } = this.props
dispatch(this.action); // 發出第一個 action(函數)
}
action = (dispatch, getState) => setTimeout(() => {
dispatch({ type: 'REQUESTSTART' })
}, 1000) // 發出第二個 action(普通對象)
複製代碼
異步代碼的處理必定要使用 redux-thunk
嗎?
非也。在觸發含有異步代碼的函數執行時,把 dispatch
函數做爲一個參數傳給函數,而後這個異步函數裏面在合適的時機調用 dispatch
發出 action
就行。
上面的異步代碼可改寫以下:
handleClick = () => {
const { dispatch } = this.props
this.action(dispatch);
}
action = dispatch => setTimeout(() => {
dispatch({ type: 'REQUESTSTART' })
}, 1000)
複製代碼
不過相比 redux-thunk
有個缺陷就是不能獲取 getState
這個方法。