redux源碼分析(2) - createStore

1、前言

  下邊章節中將詳細分析源碼,源碼分析中對於一些邊界的判斷、類型判斷等不做重點分析,主要將分析的重點放在主流程方向上。

2、createStore

   createStore 作爲 Redux 的核心 api 之一,其作用是通過 reducer 和 中間件 middleware 構造一個爲 store 的數據結構。關於 createStore 的使用可參考上一章節 Redux源碼分析(1) - Redux介紹及使用 和 官方文檔 createStore

  createStore 的源碼結構圖如下圖所示。根據是否傳入enhancer,分別做不同的邏輯判斷
createStore源碼結構圖

2.1 存在enhancer

   當存在enhancer的時候,實際執行語句如下:

//createStore.js

return enhancer(createStore)(reducer, preloadedState);  //記爲語句(1)

   在上一章節中,我們介紹過 Redux 的如何使用:

//demo

let store = createStore(rootReducer, applyMiddleware(Logger, Test));   //記爲語句(2)

   由語句(2)可知:enhancer 其實就是 applyMiddleware 作用中間件(此處爲Logger, Test)的結果。查看 applyMiddleware 源碼,大致結構如下:

//applyMiddleware.js

function applyMiddleware(...middlewares){
    //createStore中對於的enhancer
    return createStore => (...args) => {
    //...
      return {
        ...store,
        dispatch  // 覆蓋store中的dispatch
      };
    }
}

   由 applyMiddleware 的源碼可知。語句(1)執行的就是 applyMiddleware 返回高階函數的完整執行,最終返回的結果是包含 store 所有屬性 和 dispatch屬性的一個對象,這也與沒有enhancer時,createStore輸出的結果保持之一。因爲 store 對象中本身就存在 dispatch 屬性,由此可知 :

   applyMiddleware 作用中間件的結果就是更改 store 對象的dispatch。這是前文提到過的,applyMiddleware 本質是對 dispatch 的增強。至於是如何增強的,在下文會詳細分析。

2.2 不存在enhancer

   首先看下此時對應的返回結果

//createStore.js

return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
};
2.2.1 方法內的局部變量
let currentReducer = reducer;  // 臨時reducer
let currentState = preloadedState; //當前的state值,默認爲初始化preloadedState
let currentListeners = [];  // 監聽隊列,用於存放監聽事件, 發佈-訂閱模式
let nextListeners = currentListeners; // 淺拷貝下這個隊列
let isDispatching = false; // 標誌位,用來判斷是否有存在正在執行的dispatch

   各個變量的作用,註釋中已經詳細註明,不再贅述。

2.2.2 getState

   根據 Redux api 可知:getState 方法返回當前的 state 樹。currentState 默認值 爲 preloadedState,具體的
currentState 取決於 dispatch(action) 時 reducer 執行後返回的結果。其中如果 isDispatching 爲 true 時,表示有 dispatch 正在執行,此時獲取 state 的值會導致獲取不到正確的 state。


function getState() {
    if (isDispatching) {
        .....
    }
    
    return currentState;
}
2.2.3 subscribe

   先來看看subscriber的使用,其中 listener 表示監聽觸發時,需要做的一些操作。


let unsubscribe = store.subscribe(listener)
unsubscribe()

   subscribe 的源碼如下:


function subscribe(listener) {
    // 類型判斷
    if (typeof listener !== 'function') {
        throw new Error('Expected the listener to be a function.');
    }

    // 同理不可以dispatch中
    if (isDispatching) {
        //……
    }

    // 用來表示訂閱標記,用於避免取消訂閱後再次取消
    let isSubscribed = true;
    // ensureCanMutateNextListeners幹啥的,點擊去看一下
    ensureCanMutateNextListeners();
    // 將 listener 存在在 發佈-訂閱模式的監聽隊列 nextListeners 中
    nextListeners.push(listener);
    // 返回取消的function(unsubscribe)
    return function unsubscribe() {
        // 如果已經取消訂閱 直接直接return
        if (!isSubscribed) {
            return;
        }

        // 同理
        if (isDispatching) {
            //……
        }

        // 這裏標記爲 已經取消訂閱
        isSubscribed = false;
        // 保存訂閱快照
        ensureCanMutateNextListeners();
        // 根據索引 在監聽隊列裏刪除監聽
        const index = nextListeners.indexOf(listener);
        nextListeners.splice(index, 1);
    };
}

   通過源碼的解讀可知。listener 必須傳入一個 function 類型,否則就會報錯。這裏用到了發佈-訂閱模式,執行 subscribe 方法時,將傳入的 listener 存放在 監聽隊列 nextListeners 裏,currentListeners 和 nextListeners 都是引用類型,都是指向的一個內存地址,可以理解爲是一個東西。

   返回值是一個 unsubscribe 函數。執行該函數,就能夠取消訂閱。具體來看首先判斷 isSubscribed 是否爲 false,如果是則代表已經取消了該訂閱,再次執行改訂閱則直接忽視。如果 isSubscribed 爲 ture 則表示該訂閱還存在,則通過 indexOf 方法找到索引後,通過 splice 方法,將該訂閱從訂閱隊列中取消,同時不要忘記將 isSubscribed 設置爲已經 false (表示已經取消)。

2.2.4 dispatch

   dispatch的使用方式如下。分發 action是觸發 state 變化的惟一途徑。匹配到對應的 reducer 執行之後,會返回一個新的 state。

store.dispatch(action)

   dispatch 的源碼如下:

function dispatch(action) {
        // acticon必須是由Object構造的函數, 否則throw Error
        if (!isPlainObject(action)) {
            // 拋出錯誤
        }

        // 判斷action, 不存在type throw Error
        if (typeof action.type === 'undefined') {
           // 拋出錯誤 'Have you misspelled a constant?'
            );
        }

        // dispatch中不可以有進行的dispatch
        if (isDispatching) {
           // 拋出錯誤
        }

        try {
            // 執行時標記爲true
            isDispatching = true;
            // reducer形式如下:(state,action)=>{} , reducer本身就是個函數,由此可見dispatch(action), 就是執行reducer方法,並將currentState,action作爲參數
            currentState = currentReducer(currentState, action);
        } finally {
            // 最終執行, isDispatching標記爲false, 即完成狀態·
            isDispatching = false;
        }

        // 循環監聽隊列,並執行每一個監聽事件
        const listeners = (currentListeners = nextListeners);
        for (let i = 0; i < listeners.length; i++) {
            const listener = listeners[i];
            // 執行每一個監聽函數
            listener();
        }
        // 返回傳入的action
        return action;
    }

   通過源碼的解讀可知。action 必須是一個純粹的對象且必須包含type屬性,否則就出拋出異常。dispatch 方法幹了兩件事情:

  • 1、找到對應的 reducer 根據 action.type 執行對應的分支,並返回最新的 state 。 爲什麼說對應的 reducer,因爲 reducer 有可能通過 combineReducers 組合的,此時 action.type 只會更改對應的 reducer 的返回值。
currentReducer(currentState, action);

如果 reducer 不是組合生成,以 reducer/todos 爲例。則  currentReducer 就是如下:

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO': //……

    case 'TOGGLE_TODO': //……
     
    default:
      return state
  }
}

如果 reducer 組合生成,也即本例中 rootReducer (通過 combineReducers 組合)。查看 combineReducers 源碼可知,  combineReducers 組合各個 reducer 後返回的是一個名爲 combination 的高階函數,  也就是currentReducer ,有如下形式:

function combination(state = {}, action) {
    // ……
    return state
}

二者具有相同的形式

(state , action ) =>{ // 更改 state的邏輯}

   通過上邊的僞代碼分析可以,不管是否通過 combineReducers 組合生成, currentReducer 都具有相同的形式, 本身作爲一個函數接受參數 state 和 action ,並根據action,改變state的值。因此可以認爲,dispatch(action) 時,本質就是 reducer 方法的執行,並將當前的 stare 值 currentState 和 action 作爲參數,並返回一個新的 state。

  • 2、執行通過 subscribe 方法添加的監聽, 通過 subscribe 方法添加的監聽都被記錄在監聽隊列 currentListeners 中,在dispatch 方法會循環遍歷監聽隊列,並以此執行各個隊列元素。
2.2.5 replaceReducer

   在 createStore 方法中,還要其他一些我們不常用的 api

  • replaceReducer : 更改 reducer

這是一個高級 API。只有在你需要實現代碼分隔,而且需要立即加載一些 reducer 的時候纔可能會用到它。在實現 Redux 熱加載機制的時候也可能會用到observable:

3、reducer 的初始化。

dispatch({ type: ActionTypes.INIT });


const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  //……
}

   由上邊的代碼可知,初始化 reducer 時是通過 dispatch 一個隨機產生的 action 。根據 reducer 的特性可知,當前初始化的 dispatch 會執行對應的 default 分支,也即會輸出 reducer 中默認的 state 的值。

   與 createStore 參數 preloadedState 的對比:在 createStore 方法的定義中可以接受一個preloadedState 參數,該參數會默認爲當前的 state。

let currentState = preloadedState; //當前的state值,默認爲初始化preloadedState

   通過 dispatch 的流程可知,初始化dispatch時,preloadedState 會作爲 reducer 方法執行的參數傳入。當 preloadedState 不存在時,此時 reducer 的入參爲 undefined。通常做法如下, 通過 es6 的默認參數給state 複製初始值,則能起到 preloadedState 的效果。猜測 Redux 這些寫應該是爲了兼容 es5 的默認值處理吧。

const todos = (state = [], action) => {//.......}

   但是不管 preloadedState 指定與否,初始化dispatch 執行後,currentState 的值即爲default 分支對應的值。由此可知,我們定義的 reducer 都要包含 default 分支,否則初始化後 state 的值就會出現異常。


下一章節將分析applyMiddleware,更新中。。。。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章