Redux原始碼分析(2) - createStore
1、前言
下邊章節中將詳細分析原始碼,原始碼分析中對於一些邊界的判斷、型別判斷等不做重點分析,主要將分析的重點放在主流程方向上。
2、createStore
createStore 作為 Redux 的核心 api 之一,其作用是通過 reducer 和 中介軟體 middleware 構造一個為 store 的資料結構。關於 createStore 的使用可參考上一章節 Redux原始碼分析(1) - Redux介紹及使用 和 官方文件 createStore 。
createStore 的原始碼結構圖如下圖所示。根據是否傳入enhancer,分別做不同的邏輯判斷
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 的值就會出現異常。
Redux原始碼分析(1) - Redux介紹及使用
redux原始碼分析(2) - createStore
Redux原始碼分析(3) - applyMiddleware
Redux原始碼分析(4) - combineReducers和 bindActionCreators
相關文章
- Redux原始碼分析之createStoreRedux原始碼
- Redux原始碼解讀--(5)createStoreRedux原始碼
- Redux原始碼createStore解讀常用方法Redux原始碼
- React-Redux 原始碼解析 一(createStore)ReactRedux原始碼
- 逐行閱讀redux原始碼(一) createStoreRedux原始碼
- redux原始碼分析Redux原始碼
- Redux 原始碼分析Redux原始碼
- Redux-實現createStoreRedux
- Redux原始碼分析(一)Redux原始碼
- 封裝redux中的createStore封裝Redux
- Redux原始碼分析–Reducer篇Redux原始碼
- redux middleware 原始碼分析Redux原始碼
- Redux原始碼分析--bindActionCreators篇Redux原始碼
- Redux 高階 -- 原始碼分析Redux原始碼
- React-Redux原始碼分析ReactRedux原始碼
- redux v4.0.0 原始碼分析Redux原始碼
- 大道至簡之redux原始碼分析Redux原始碼
- Redux原始碼分析–資料中心篇Redux原始碼
- Redux原始碼分析--資料中心篇Redux原始碼
- React-Redux v5 原始碼分析ReactRedux原始碼
- redux原始碼分析之五:applyMiddlewareRedux原始碼APP
- Redux Middleware中介軟體原始碼 分析Redux原始碼
- 集合原始碼分析[2]-AbstractList 原始碼分析原始碼
- redux && react-redux原始碼解析ReduxReact原始碼
- Redux 原始碼解讀 —— 從原始碼開始學 ReduxRedux原始碼
- 通過GitHub Blame深入分析Redux原始碼GithubRedux原始碼
- redux原始碼分析之四:compose函式Redux原始碼函式
- 一步一步分析Redux原始碼?Redux原始碼
- Redux原始碼初探Redux原始碼
- redux原始碼解析Redux原始碼
- redux真的不復雜——第二篇:react-redux原始碼分析ReduxReact原始碼
- Mysql原始碼分析2MySql原始碼
- react-redux原始碼分析及實現原型(上)ReactRedux原始碼原型
- react-redux原始碼分析及實現原型(下)ReactRedux原始碼原型
- 前端妹紙的進階之路——redux原始碼分析前端Redux原始碼
- 全面剖析 Redux 原始碼Redux原始碼
- Redux 原始碼全面解析Redux原始碼
- Redux原始碼淺析Redux原始碼