寫在前面
寫React也有段時間了,一直也是用Redux管理數據流,最近正好有時間分析下源碼,一方面希望對Redux有一些理論上的認識;另一方面也學習下框架編程的思維方式。
Redux如何管理state
注冊store tree
1、Redux通過全局唯一的store對象管理項目中的state
var store = createStore(reducer,initialState);
2、可以通過store注冊listener,注冊的listener會在store tree每次變更后執行
store.subscribe(function () {
console.log("state change");
});
如何更新store tree
1、store調用dispatch
,通過action把變更的信息傳遞給reducer
var action = { type: 'add'};
store.dispatch(action);
2、store根據action攜帶type
在reducer中查詢變更具體要執行的方法,執行后返回新的state
export default (state = initialState, action)=>{
switch (action.type) {
case 'add':
return {
count:state.count + 1
}
break;
default:
break;
}
}
3、reducer執行后返回的新狀態會更新到store tree中,觸發由store.subscribe()
注冊的所有listener
Store實現
主要方法:
- createStore
- combineReducers
- bindActionCreators
- applyMiddleWare
- compose
createStore源碼分析
查看完整createStore
請戳這里
createStore
方法用來注冊一個store,返回值為包含了若干方法的對象,方法體如下:
export var ActionTypes = {
INIT: '@@redux/INIT'
}
export default function createStore(){
function getState(){}
function dispatch(){}
function subscribe(){}
function replaceReducer(){}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer
}
}
下面逐個代碼段分析功能
createStore
完整函數聲明如下:
createStore(
reducer:(state, action)=>nextState,
preloadedState:any,
enhancer:(store)=>nextStore
)=>{
getState:()=>any,
subscribe:(listener:()=>any)=>any,
dispatch:(action:{type:""})=>{type:""},
replaceReducer:(nextReducer:(state, action)=>nextState)=>void
}
可以看出整個函數是一個閉包結構。參數有三個,返回值公開出若干方法
- dispatch:分發action
- subscribe:注冊listener,監聽state變化
- getState:讀取store tree中所有state
- replaceReucer:替換reducer,改變state更新邏輯
當然,createStore
內部處理了其重載形式,即:可以不傳preloadedState
createStore(
reducer:(state, action)=>nextState,
enhancer:(store)=>nextStore
)
參數:
- reducer: reducer必須是一個function類型,此方法根據action.type更新state
- preloadedState: store tree初始值
- enhancer: enhancer通過添加middleware,增強store功能
前置操作
進入createSore
首先執行如下操作:
- [40-43] 用於支持兩種參數列表形式
createStore(reducer,preloadedState,enhancer)
和createStore(reducer,enhancer)
- [45-48][53-55] 校驗reducer和enhancer的類型(必須為function)
重點分析下50行:
return enhancer(createStore)(reducer, preloadedState)
本語句執行了外部傳入的enhancer,接收舊createStore
,返回一個新createStore
並執行,此過程形成一次遞歸;
那么遞歸什么時候停止呢?
可以看到,新createStore
執行時,僅有reducer和preloadedState兩個參數,再次運行到45行時,不會進入if條件 故不會再形成第二次遞歸,此時遞歸停止;
理論上,createStore
僅被增強了一次,那如果希望對其進行多次增強該怎么辦呢?
Redux提供了compose
和applyMiddleWare
方法,用來在Store上注冊中間件,由此來實現多次增強。
getState()
getState
方法比較簡單,直接返回當前store tree狀態
- [57-61] 定義了createStore內部要用到的全局變量。其中
currentReducer
、currentState
聲明當前reducer方法集合和store tree狀態,初始值為外部傳入的createStore
參數,currentListeners
和nextListeners
定義了存放store變化時要執行響應函數的數組集合
subscribe()
Redux采用了觀察者模式,store內部維護listener數組,用於存儲所有通過store.subscrib
注冊的listener,store.subscrib
返回unsubscrib
方法,用於注銷當前listener;當store tree更新后,依次執行數組中的listener
具體代碼如下:
dispatch()
dispatch
方法主要完成兩件事:
1、根據action查詢reducer中變更state的方法,更新store tree
2、變更store tree后,依次執行listener中所有響應函數
- [168-173] 通過currentReducer和action,更新當前的store tree
- [175-181] 當state tree變更后,依次執行所有注冊的listener
有個問題需要注意:
方法中使用了全局定義的isDispatching
用於給變更中的store tree加鎖;即:只有當本次store tree變更完畢后,才允許執行下一次變更,避免store tree響應多個變更時,結果不同步的問題;但事實上,這種寫法也決定了,目前的store tree只能響應同步變更(異步變更需要通過添加中間件實現)
replaceReducer()
replaceReducer
用於替換操作store tree中state的方式
整個方法代碼量不多,從外部接收新的reducer方法后,替換掉內部舊的ruducer。
需要注意一下199行的dispatch
方法,這一行主動觸發了一次變更。由於每次dispatch
執行后,redux都會執行reducer或子reducer方法(如果使用了combineReducers
),所以這一行的作用就是在初始化store tree中所有的state節點。
小結
以上就是整個createStore
方法的主要實現過程,其中dispatch
方法為控制整個store tree變更的核心方法。觸發store tree變更的方式只有一個,就是dispatch
一個action
combineReducers源碼分析
為什么需要combineReducers
結合上面store tree變更的過程,我們可以看到,真正導致變更的核心代碼就是:
currentState = currentReducer(currentState, action)
試想,若整個項目只通過一個reducer方法維護整個store tree,隨着項目功能和復雜度的增加,我們需要維護的store tree層級也會越來越深,當我們需要變更一個處於store tree底層的state,reducer中的變更邏輯會十分復雜且臃腫。
而combineReducers
存在的目的就是解決了整個store tree中state與reducer一對一設置的問題。我們可以根據項目的需要,定義多個子reducer方法,每個子reducer僅維護整個store tree中的一部分state, 通過combineReducers
將子reducer合並為一層。這樣我們就可以根據實際需要,將整個store tree拆分成更細小的部分,分開維護。
代碼實現
combineReducers
完整代碼請戳這里
整個函數體結構如下:
combineReducers(
reducers:Object
)=> reducer(
state:any,
action:{type:""}
)
參數reducers是一個Object對象,其中包含所有待合並的子reducer方法
返回值是合並后的reducer方法,在執行此方法時,會在已合並的所有子reducer中查詢要執行的reducer,並執行,變更其對應的state片段。
下面逐個代碼段分析具體實現:
以上這部分主要用於規范化存儲子ruducer的reducers對象
- [103-115] 過濾掉reducers對象中所有非function類型的reducer,合法的結果保存在
finalReducers
對象中 - [116-127] 通過
assertReducerSanity
方法校驗所有子reducer的初始值和執行后結果是否為空,是則提示錯誤。
以上這段代碼為combineReducers
的核心代碼,其返回一個function,用於查詢真正要變更的state片段
- [141-154] 遍歷規范化后的
finalReducers
,獲取到當前key對應的子reducer和子state,執行reducer得到當前state片段更新后的狀態,並更新到整個store tree中。
其中129行中的state,應該是整個store tree對應的state,首次獲取previousStateForKey
時,值可能為undefined,那么接下來執行var nextStateForKey = reducer(previousStateForKey, action)
實際上是依次為每個子state片段進行初始化。 - [153] 本行判定store tree是否被更新,其中
nextStateForKey !== previousStateForKey
直接通過引用關系判斷state是否變更。故一定要注意,定義reducer方法時,一定要遵循函數式編程,確保傳入的state與返回的state不要存在引用關系,否則可能導致store tree中狀態無法更新。
小結
至此,我們可以看到combineReducers
方法,實際就是在每次要執行reducer時,通過action.type定義的類型進行查詢,獲得子reducer並執行。
通過以上分析,我們需要注意兩個問題:
1、子reducer遵循函數式編程,不要直接變更作為參數傳入的state,變更state后,一定要返回一個新state對象,不要跟參數state建立引用關系(可以使用Immutable處理state)
2、由於combineReducers
內部僅通過action.type作為查詢當前要執行的子reducer的依據,會更新所有查詢到的state片段,故不建議子reducer中,action.type的值出現重復,否則可能會誤更新state。
總結
本篇通過分析源碼整理了Redux中Store對象的執行邏輯,重點分析了dispatch(action)后,store tree內部狀態如何更新。
篇幅所限,沒有分析如何在store上注冊中間件,以及如何在store tree變更后,觸發頁面更新的過程,這些會在之后的博客中更新
第一次寫源碼分析還有很多不足,如有錯誤,歡迎指正。