源碼系列:javascript
下面是 Redux v4.x API 源碼的解讀。java
createStore(reducer, [preloadedState], enhancer)react
createStore
方法接受三個參數,第一個參數爲 reducer
,第二個參數是 preloadedState
(可選),第三個是參數是 enhancer
。返回一個 對象 store
。store
中包含方法 dispatch
、getState
、subscribe
、replaceReducer
、[$$observable]
。git
參數 reducer
有多是一個 reducer
,若是有個多個 reducer
,則經過 combineReducer
函數執行後返回函數做爲 reducer
。github
參數 preloadedState
表明初始狀態,不少時候都不傳,不傳該參數時候且 enhancer
是函數狀況,createStore
函數內部會把 enhancer
,做爲第二個參數使用,源碼以下:編程
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
複製代碼
參數 enhancer
是 store
加強器,是一個函數, createStore
做爲 enhancer
函數的參數,這裏用到了函數式編程,返回函數又傳遞 reducer
和 preloadState
參數,執行最終返回一個加強的 store
。enhancer
常有的是 applyMiddleware()
返回值,react-devrools()
等工具,源碼以下:redux
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
複製代碼
介紹方法以前,下面是一些變量:api
let currentReducer = reducer // 當前的 reducer
let currentState = preloadedState // 當前的 state
let currentListeners = [] // 當前的監聽者
let nextListeners = currentListeners // 監聽者的備份
let isDispatching = false // 是否正在執行 dispatch 動做
複製代碼
dispatch(action)數組
dispatch
方法,接受一個參數action
,action
是一個對象,該對象必須包括 type
屬性,不然會報錯。dispatch
函數內部,主要是執行了 reducer()
函數,獲得最新 state
,賦值給 currentState
,執行訂閱者,dispatch
函數返回一個 action
對象。源碼以下:promise
function dispatch(action) {
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) { // 若是正在 dispatch,再次觸發 dispatch,則報錯
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true // 表示正在執行 `dispatch`
currentState = currentReducer(currentState, action) // 執行最新的 Reducer,返回最新的 state
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners) // 每次 dispatch 執行,訂閱者都會執行
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
複製代碼
getState()
獲取最新的 state,就是返回 currentState。源碼以下:
function getState() {
return currentState
}
複製代碼
添加事件訂閱者,每次觸發 dispatch(action)
時候,訂閱者都會執行。返回取消訂閱方法 unSubscribe
。這裏採用了觀察者模式/發佈-訂閱者模式。源碼以下:
注意:訂閱者必須是函數,不然報錯。
function subscribe(listener) {
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.'
)
}
let isSubscribed = true
nextListeners.push(listener) // 訂閱消息,每次 dispatch(action) 時候,訂閱者都會接受到消息
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
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1) // 取消訂閱者
}
}
複製代碼
讓一個對象可被觀察。被觀察對象必須是對象,不然報錯。這裏做者用到了第三方庫 symbol-observable
,對 currentState
進行觀察。源碼以下:
ECMAScript Observables
目前只是草案,還沒正式使用。
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()) // 對 currentState 進行監聽
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
複製代碼
替換reducer
,通常用不到。源碼也簡單,把 nextReducer
賦值給 currentReducer
。源碼以下:
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
}
複製代碼
上面 dispatch
、getState
、subscribe
、replaceReducer
、[$$observable]
這些方法使用了閉包,一直保持對 currentState
、currentReducer
、currentListeners
、nextListeners
、isDispatching
等變量的引用。好比,dispatch
改變 currentState
,相應的其餘方法中,currentState
也會跟着變,因此這些方法之間是存在聯繫的。
執行中間件的方法,中間件能夠有不少,有自定義的如:打印日誌,也有比較知名的 如:redux-chunk
、redux-promise
、redux-saga
等,後續會分析。
applyMiddleware()
一般是上面提到的 createStore
方法的第三個參數 enhancer
,源碼以下:
enhancer(createStore)(reducer, preloadedState)
複製代碼
結合上面代碼能夠等價於
applyMiddleware(...middlewares)(createStore)(reducer, preloadedState)
複製代碼
applyMiddleware
使用了函數式編程,接收第一個參數元素爲 middlewares
的數組,返回值接收createStore
爲參數的函數,返回值接收 dispatch
函數,接收一個 action
如 (reducer, state)
,最終返回加強版的 store
。
源碼以下:
function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args) // args 爲 (reducer, initialState)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = { // 給 middleware 的 store
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI)) // chain 是一個參數爲 next 的匿名函數的數組(中間件執行返回的函數)
dispatch = compose(...chain)(store.dispatch) // 通過 compose 函數後返回的結果,是通過 middlewares 包裝後的 dispatch
return { // 返回 dispatch 加強後的 store
...store,
dispatch
}
}
}
複製代碼
源碼以下:
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)))
}
複製代碼
雖然源代碼沒幾行,做者實現方式太厲害了,若是理解起來有點費勁,能夠看看下面例子分析:
var arr = [
function fn1(next){
return action => {
// next 爲 fn2 return 函數(dispatch 函數)
const returnValue = next(action)
return returnValue
}
},
function fn2(next) {
return action => {
// next 爲 fn3 return 函數(dispatch 函數)
const returnValue = next(action)
return returnValue
}
},
function fn3(next3) {
return action => {
// next 爲 dispatch。
const returnValue = next3(action)
return returnValue
}
},
]
var fn = arr.reduce((a, b) => (args) => a(b(args)))
fn // (args) => a(b(args)) 等價於 (dispatch) => fn1(fn2(fn3(dispatch)))
// reduce函數 執行順序:
// 第一次:
// 變量 a: ƒ fn1(args)
// 變量 b: ƒ fn2(args)
// 返回值: (args) => fn1(fn2(args))
// 第一次返回值賦值給 a
// 變量 a: (args) => fn1(fn2(args))
// 變量 b: ƒ fn3(args)
// 第二次:
// 變量 a : (args) => fn1(fn2(args))
// 變量 b: ƒ fn3(args)
// 返回值: (args) => fn1(fn2(fn3(args)))
複製代碼
因此做者的用意是 compose(f, g, h)
返回 (...args) => f(g(h(...args)))
,其中 f、g、h
是一個個中間件 middleware
。
結合上面 applyMiddleware
源碼有一段 dispatch = compose(...chain)(store.dispatch)
,因此 args
爲 store.dispatch
,(store.dispatch) => f(g(h(store.dispatch)))
,執行第一個中間件h(store.dispatch)
,返回 dispatch
函數(參數爲 action 的函數)做爲下一個中間件的參數,經過一層層的傳遞,最終返回一個通過封裝的 dispatch
函數。
下面是打印日誌中間件例子:
const logger = store => next => action => {
console.log('dispatch', action)
next(action)
console.log('finish', action)
}
const logger2 = store => next2 => action2 => {
console.log('dispatch2', action2)
next2(action2)
console.log('finish2', action2)
}
...
const store = createStore(rootReducer, applyMiddleware(logger, logger2))
複製代碼
注意:但若是在某個中間件中使用了 store.dipsatch()
,而不是 next()
,那麼就會回到起始位置,會形成無限循環了。
每次觸發一個 dispatch,中間件都會執行,打印的順序爲 dispatch distapch2 finish2 finish
。
這個 API 理解爲:若是有多個 reducer,那麼須要把它們合成一個 reducer,在傳給函數 createStore(reducer)
。
核心源碼以下:
function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key] // reducer 必定是函數
}
}
const finalReducerKeys = Object.keys(finalReducers)
return function combination(state = {}, action) { // 返回一個新的 reducer 函數
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) { // 每次執行一次 dipatch(action),全部的 reducer 都會執行
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state // 判斷 state 是否有變化,若是有則返回新的 state
}
}
複製代碼
這個 API 能夠理解爲:生成 action 的方法。主要是把 dispatch 也封裝進 bindActionCreator(actionCreator, dispatch)
方法裏面去,因此調用時候能夠直接觸發 dispatch(action)
,不須要在手動調用 dispatch,好比 dispatch(fetchPeople({type: TYPE, text: 'fetch people'}))
,使用這個 API 後,則直接 fetchPeople({type: TYPE, text: 'fetch people'})
源碼以下:
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
const boundActionCreators = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
複製代碼
下面是博客地址,以爲不錯點個Star,謝謝~