ps: 由於餘下的源碼皆是短篇幅,因而一併合爲一文進行書寫,若有不足之處望各位指正。html
相信你們在閱讀了以前的文章以後,對什麼是action
已經很熟悉了,咱們的action
其實就是一個object
,一般其中會包含一個type
參數和一個數據載體payload
參數,用來給redux
在使用dispatch
更新狀態樹時指一條明路。react
那麼什麼是actionCreators
呢?git
那麼從字面而言,actionCreators
能夠理解爲:動做建立器,也就是說咱們的actionCreators
是用來生成action(動做)
的。咱們在業務開發的時候,一般會使用不少不一樣的action
,可是這些action
不少時候都會包括一個不定變量,好比咱們在更新一個列表的時候,咱們可能會使用這樣一個action
:編程
async function getListData() {...}
await const listData = getListData();
dispatch({
type: 'updateList',
data: listData
});
複製代碼
雖然寫一個匿名的action
也ok,可是在大型項目這樣寫的話維護將存在極大問題的,你根本就不知道這個action有什麼做用(大部分狀況下),因此咱們在統一管理這類action
時一般使用actionCreators
(固然也爲了讓action
變得更加靈活):redux
// actionCreator一般爲純函數
function updateList(listData) {
return {
type: 'updateList',
data: listData
}
}
複製代碼
咱們在書寫頁面組件的時候,若是要用react-redux
更新咱們的狀態樹而後重渲染頁面,一般會使用redux
綁定在頁面的props
上的dispatch
方法觸發action
。數組
當咱們的頁面上不存在子組件,或者子組件中不須要使用redux
時這樣作是沒有任何問題的,可是當咱們須要在子組件中使用dispatch
時,咱們就會發現,子組件的Props
是純淨的,沒有任何地方能夠調用dispatch
。bash
固然你也能夠把父組件中的dispatch
方法經過props
傳遞到子組件中使用,可是正確的操做,仍是使用咱們的bindActionCreators
:app
咱們能夠將咱們須要使用的actionCreators
封裝至一個對象中,key
值就是actionCreators
的函數名,值就是對應其函數:async
function action1(param){...}
function action2(param){...}
const actionCreators = {
action1,
action2
}
複製代碼
而後咱們使用bindActionCreators
:ide
const { dispatch } = this.props;
const dispatchActionsObject = bindActionCreators(actionCreators, dispatch);
//dispatchActionsObject爲:
{
action1: (param) => {dispatch(action1(param))},
action2: (param) => {dispatch(action2(param))}
}
複製代碼
咱們只須要將dispatchActionsObject
經過props
綁定到子組件上,那麼子組件就能夠在不知道是否存在redux
的狀況下使用咱們提供的函數來更新狀態樹了。
// 父組件
...
class Parent extends React.Component {
...
render(
<Parent>
<Child {...dispatchActionsObject}></Child>
</Parent>
)
}
// 子組件
class Child extends React.Component {
...
doAction1(data) {
this.props.action1(data)
},
doAction2(data) {
this.props.action2(data)
}
}
複製代碼
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
複製代碼
在bindActionCreators
的開始部分,咱們會發現這個方法bindActionCreator
接受了兩個參數,一個actionCreator
,一個dispatch
。
隨後其做爲高階函數,返回了一個新的匿名函數,並在匿名函數中執行了dispatch
操做,使用Function.prototype.apply
,接受了外部的傳參值,並傳入actionCreator
中生成action
。
因此這個函數的功能就是:將傳入的單個actionCreator
封裝成dispatch
這個actionCreator
的函數。 例如:
const actionCreator(data) {
return {
type: 'create',
data
}
};
const dispatch = this.props.dispatch;
const dispatchActionCreatorFn = bindActionCreator(actionCreator, dispatch);
// 這個方法會返回一個function
// actionObj = (data) => {
// dispatch(actionCreator(data);
// }
複製代碼
咱們的bindActionCreators
與bindActionCreator
入參的差異就在於第一個參數,bindActionCreator
接受的是一個function
,而bindActionCreators
則接受的是function
或者object
:
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
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"?`
)
}
...
}
複製代碼
從源碼上看,咱們的bindActionCreators
作了一些簡單的防錯和兼容處理,當接受的是function
時,其做用和直接bindActionCreator
是一致的,都是返回一個隱式調用dispatch
的方法,而當其傳入的是一個object
時,會強制阻止用戶使用null
爲參數傳入。
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
複製代碼
在防錯處理以後,即是咱們整個bindActionCreators
的核心部分。咱們會經過Object.keys()
遍歷獲取actionCreators
對象的key
值數組,並聲明一個空對象boundActionCreators
準備用來存放咱們即將生成的隱式調用dispatch
的方法。
其後咱們經過遍歷key
值數組,將每一個key
值對應的actionCreator
取出,簡單的判斷actionCreator
是否合規,而後使用bindActionCreator
生成對應的匿名函數,並存放在咱們以前聲明的boundActionCreators
的同key
值之下,這以後,咱們的bindActionCreators
完成了~
由於applyMiddleware
依賴於函數式編程的compose(組合)
,咱們先從compose
入手吧~
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)))
}
複製代碼
compose
是個很是簡單的純函數,其做用是經過reduce
將傳入的方法按照從右到左的順序組合起來。
實現過程讓咱們一步一步來嘗試:
ps: 但願你在閱讀compose源碼以前先了解rest
和箭頭函數
reduce
遍歷數組中的function
,咱們能夠用簡單的例子來看看到底發生了什麼function addOne(x) {
return x+1;
}
function double(x) {
return x*2;
}
function println(x) {
retrun x;
}
const funcs = [println, double, addOne];
複製代碼
當咱們第一次reduce
的時候:
// 由於不存在 `initialValue`
// 因此第一次reduce的第一個參數能夠視做`currentValue`
// 第二個參數視做`nextValue`
[println, double, addOne].reduce((a,b) => {
return (...args) => {a(b(...args))}
})
// 得出的結果是
[println, double, addOne].reduce((a,b) => {
return (...args) => {print(double(...args))}
})
複製代碼
此時咱們的reduce
能夠看做是:
[ (...args) => {print(double(...args))}, addOne].reduce((a,b) => {...})
複製代碼
由於此時reduce
回調中的第一個參數會變成上一次遍歷時的返回值,因此接下來的reduce
會變成這樣:
[ (...args) => {print(double(...args))}, addOne].reduce((a,b) => {
// 此時 a = (...args) => {print(double(...args));
// b = addOne
// 因此 a(b(..args)) = print(double(addOne(...args)))
return (...args) => {
print(double(addOne(..args)))
}
})
複製代碼
由此能夠看出,咱們的compose雖然簡單,可是實現的功能確是很強大的,其像洋蔥同樣,一層一層的向外調用,每個函數的結果都做爲上外層函數的入參被調用,直到最後得出結果。
在說applyMiddlewares
以前,讓咱們先回憶一下咱們的createStore
中傳入了enhancer
後的處理過程:
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'
)
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
複製代碼
能夠看到,當createStore
接收到可用的enhancer
時,會將creteStore
做爲參數傳入高階函數enhancer
中,並使用以前傳入的reducer/preloadedState
繼續進行建立store
.
而實現enhancer
的方法,就是applyMiddleware
.
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
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)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
複製代碼
能夠從源碼看到,咱們的applyMiddleware
返回的即是一個柯里化的高階函數,其接受入參的順序也符合enhancer
在被調用時的順序,因此咱們不妨直接把...args
直接替換成reducer, preloadedState
.此時,咱們的createStore
會變成這樣:
const store = createStore(reducer, preloadedState)
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)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
複製代碼
接下來會建立一個拋錯的dispatch
,阻止用戶在沒有處理完middleware
時調用真正的dispatch
影響狀態樹致使中間件middleware
不能獲取實時的狀態值。
接下來,其建立了一個middlewareAPI
,用來存放當前store
的狀態值和以前會拋錯的dispatch
,並做爲參數,結合middlewares
生成一組匿名函數,每一個匿名函數的返回值都是經過當前middleware
處理了middlewareAPI
的返回值,以後經過compose
,將這些匿名函數組合,讓其可以從右到左的鏈式調用,最後再爲組合生成的函數傳入store.dispatch(真正的disaptch)
做爲參數值,其過程以下:
// 假設我有兩個middleware: mdw1, mdw2
// compose過程以下:
// 而mdw1, mdw2通過遍歷是以下的數組
[mdw1(middlewareAPI), mdw2(middlwwareAPI)]
// 通過compose後
return (...args) => mdw1(middlewareAPI)(mdw2(middlwwareAPI)(...args))
// 傳入store.dispatch以後
return mdw1(middlewareAPI)(mdw2(middlwwareAPI)(store.dispatch))
複製代碼
這樣處理以後,咱們經過鏈式調用中間件處理完成的dispatch
才能正式return
出去,做爲store
的一部分。
本文至此,redux
的5個文件的源碼也算是告一段落,從閱讀redux
的源碼中,學到了不少關於函數式編程的思路和一些技巧,以及對各類邊界狀況的重視。之前並不注重其中實現,以爲讓本身來也能寫出來,可是真到了本身看的時候才發現,原來仍是圖樣圖森破,也只有感嘆一句:任重道遠啊...
最後,感謝你的閱讀~