1. 程式人生 > >Redux原理(一):Store實現分析

Redux原理(一):Store實現分析

Redux原理(一):Store實現分析

寫在前面

寫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提供了composeapplyMiddleWare方法,用來在Store上註冊中介軟體,由此來實現多次增強。

getState()

getState方法比較簡單,直接返回當前store tree狀態

  • [57-61] 定義了createStore內部要用到的全域性變數。其中currentReducercurrentState聲明當前reducer方法集合和store tree狀態,初始值為外部傳入的createStore引數,currentListenersnextListeners定義了存放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。