Redux原理簡析與源碼解析

本文發佈在個人博客javascript

redux版本是最新的v4.0.5java

原理簡析

先簡單敘述一下部分的實現原理react

createStore

createStore(reducer, preloadedState, enhancer)
複製代碼

調用createStore有2種狀況,傳入了加強器enhancer時,會將createStore傳入enhancer,也就是在enhancer裏建立了store後,把store的方法加強後再返回,代碼來看就是git

enhancer(createStore)(reducer, preloadedState)
複製代碼

另外一種就是忽略掉了enhancer,直接建立storegithub

在建立完store後,內部會dispatch一個type: ActionTypes.INITaction,由於咱們的reducer不會有這個type的,因此會返回初始值,這一步就是給整個state tree賦初始值了redux

一般,咱們都不會只有一個reducer,因此須要使用redux提供的合併reducer的函數combineReducers數組

combineReducers

combineReducers的原理就是依次調用傳入對象的值的reducer函數閉包

combineReducers(reducers)
複製代碼

簡單來理解僞代碼能夠是這樣app

// 傳入
const reducers = {
  count: state => state,
  string: state => state
};

// 函數裏處理

const keys = ["count", "string"];
// 新state
const state = {};
for (const key of keys) {
  // 經過上一次key對應的state,調用對應的reducer函數,獲得新的state
  state[key]=reducers[key](prevKeyState)
}
return state;
複製代碼

applyMiddleware

applyMiddlewareredux自帶的加強器,主要是用來加強dispatch函數的功能,也就是提供了dispatch函數的中間件函數

以前有講,若是傳入了enhancer,會將createStore交給加強器來辦,好比使用的applyMiddleware,流程大概就是這樣

// createStore將本身交給了加強器
applyMiddleware(加強器A,加強器B)(createStore)(reducers,preloadedState)

// 函數聲明大概就是這樣
function applyMiddleware(加強器A,加強器B) {
  return function (createStore) {
    return function (reducers,preloadedState) {
        const state = createStore(reducers,preloadedState);
        // 取出dispatch 使用接收的加強器對他進行加強
        ...
    }
  }
}
複製代碼

接下來就直接鋪上源碼

源碼解析

如下tsc轉出的JavaScript版,而且我人爲的省略掉了一些類型判斷和拋出錯誤

本文詳細代碼見個人github倉庫

createStore

import $$observable from "./utils/symbol-observable";
import ActionTypes from "./utils/actionTypes";

export default function createStore(reducer, preloadedState, enhancer) {  
  	// 若是有加強器
  	if (typeof enhancer !== "undefined"){
    	return enhancer(createStore)(reducer, preloadedState);
    }

    // 當前的reducer
    let currentReducer = reducer;
    // 當前的state
    let currentState = preloadedState;
    // 當前的listeners
    let currentListeners = [];
    // 下一次的listeners
    let nextListeners = currentListeners;
    // 標示是否正在進行dispatch
    let isDispatching = false;

     // 這是一個對currentListeners的淺複製,因此咱們能夠將nextListeners看成一個臨時的list在dispatch的過程當中使用
     // 這樣作的目的是能夠防止在dispatch調用過程當中,調用subscribe/unsubscribe產生錯誤
    function ensureCanMutateNextListeners() {
        if (nextListeners === currentListeners) {
            // 淺複製
            nextListeners = currentListeners.slice();
        }
    }


    // 用來獲取state tree
    function getState() {
        return currentState;
    }
    
  	// 添加一個state change的監聽,它會在每次dispatch調用結束後而且一部分state tree可能被改變時調用
  	// 你能夠在這個callback裏調用getState()來獲取當前的state tree
  	// 返回值是一個函數,用來退訂
    function subscribe(listener) {
        // 標誌已經被訂閱
        let isSubscribed = true;
        // 淺複製一次listeners
        // 也就是currentListeners複製到nextListeners
        ensureCanMutateNextListeners();
        // 添加進nextListeners
        nextListeners.push(listener);
        // 返回的退訂函數
        return function unsubscribe() {
            // 若是已經退訂了,就return
            // 防止屢次調用函數
            if (!isSubscribed) {
                return;
            }
            // 已經退訂
            isSubscribed = false;
            // 淺複製一次
            ensureCanMutateNextListeners();
            const index = nextListeners.indexOf(listener);
            // 刪除掉訂閱的函數
            nextListeners.splice(index, 1);
            // currentListeners設置爲null的緣由是防止內存泄露
            // 見https://github.com/reduxjs/redux/issues/3474
            currentListeners = null;
        };
    }

     // dispatch一個action,這是觸發state改變的惟一方式
     // 它只實現了基礎的字面量對象action操做,若是你想要dispatch一個Promise、Observable、thunk獲取其餘的,你須要將建立store的函數放進響應的中間件,好比redux-thunk包
     // 爲了方便返回值爲相同的action對象
     // 若是你使用來自定義的中間件,可能會返回其餘的東西,好比Promise
    function dispatch(action) {
        try {
            isDispatching = true;
            // 經過reducer獲取下一個state
            currentState = currentReducer(currentState, action);
        }
        finally {
            isDispatching = false;
        }
        // 通知全部listeners
        const listeners = (currentListeners = nextListeners);
        for (let i = 0; i < listeners.length; i++) {
            const listener = listeners[i];
            listener();
        }
        return action;
    }
    
     // 替換store當前使用的reducer來計算state
     // 若是你的app實現了代碼分割,而且你想動態的加載某些reducers,或者實現來redux的熱重載,就須要這個方法
    function replaceReducer(nextReducer) {
      // ...
    }
    
    // 提供給observable/reactive庫的接口
    function observable() {
      // ...
    }

    // 當store建立好了,會派發一個INIT的action,這樣全部的reducer都會返回它們的初始值
    // 有效填充了初始的state tree
    dispatch({ type: ActionTypes.INIT });
    const store = {
        dispatch: dispatch,
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable
    };
    // 返回store
    return store;
}

複製代碼

combineReducers

combineReducers的做用就是將多個reducer合併爲一個

import ActionTypes from "./utils/actionTypes";

export default function combineReducers(reducers) {
    // 獲取傳入的reducers對象的keys
    const reducerKeys = Object.keys(reducers);
    // 實際使用的reducers對象
    const finalReducers = {};
    for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i];
        finalReducers[key] = reducers[key];
    }
    // 獲取reducers的key
    const finalReducerKeys = Object.keys(finalReducers);
    // 返回的合併爲一個的reducer函數
    return function combination(state = {}, action) {
        // 標示state有沒有改變
        let hasChanged = false;
        // 通過reducer處理的下一次state
        const nextState = {};
        // 循環調用每個reducer函數
        for (let i = 0; i < finalReducerKeys.length; i++) {
            // 當前reducer的key
            const key = finalReducerKeys[i];
            // 當前reducer的函數
            const reducer = finalReducers[key];
            // 當前key對應的state的值
            const previousStateForKey = state[key];
            // 通過reducer函數後的下一此state值
            const nextStateForKey = reducer(previousStateForKey, action);
            // 當前key的值賦值給state對象
            nextState[key] = nextStateForKey;
            // 若是當前key的state和上一次的state不一樣,說明state就已經改變了
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
        }
        // 若是replace了reducers,可能會須要判斷key的length
        // 見https://github.com/reduxjs/redux/issues/3488
        hasChanged =
            hasChanged || finalReducerKeys.length !== Object.keys(state).length;
        return hasChanged ? nextState : state;
    };
}
複製代碼

bindActionCreators

bindActionCreators的做用是簡化操做,能夠把dispatch包裝進咱們的action creator函數

// 綁定
function bindActionCreator(actionCreator, dispatch) {
    return function (...args) {
        return dispatch(actionCreator.apply(this, args));
    };
}
export default function bindActionCreators(actionCreators, dispatch) {
    // 只有一個函數的狀況
    if (typeof actionCreators === "function") {
        return bindActionCreator(actionCreators, dispatch);
    }
    const boundActionCreators = {};
    // 循環綁定
    for (const key in actionCreators) {
        const actionCreator = actionCreators[key];
        boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
    }
    return boundActionCreators;
}

複製代碼

compose

compose函數是中間件applyMiddleware的核心功能,能將多個單參數函數從右到左嵌套調用

它的調用形式以下

const a = a => a + "A";
const b = b => b + "B";
const c = c => c + "C";

const composed = compose(a,b,c);

composed("args"); // => argsCBA
複製代碼

源碼以下

export default function compose(...funcs) {
  	// 參數爲0個
    if (funcs.length === 0) {
        return (arg) => arg;
    }
  	// 參數爲1個
    if (funcs.length === 1) {
        return funcs[0];
    }
    return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

複製代碼

applyMiddleware

applyMiddlewareredux自帶的加強器,用來加強dispatch功能

import compose from "./compose";
/** * applyMiddleware函數接收middleware爲參數 * 返回另外一個函數,這個函數須要接收createStore爲函數,這個處理是在createStore中進行的 * * 這裏是使用接收的createStore函數,把store建立出來 * 而後把dispatch和getStore傳給中間件函數 * 使用compose把已經有dispatch和getStore方法當中間件組合後,將dispatch傳入,獲得一個新的dispatch * 新的dispatch是通過了中間件的dispatch */
export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer, ...args) => {
        const store = createStore(reducer, ...args);
        // 這裏作一個錯誤處理
        // 若是在綁定中間件的時候調用dispatch會報錯
        let dispatch = () => {
            throw new Error("...");
        };
        const middlewareAPI = {
            getState: store.getState,
            dispatch: (action, ...args) => dispatch(action, ...args)
        };
        // 將dispatch和getStore方法傳入中間件,獲得新的數組
        const chain = middlewares.map(middleware => middleware(middlewareAPI));
        // 將新的數組用compose綁定起來,再把store.dispatch傳入,獲得新的dispatch
        dispatch = compose(...chain)(store.dispatch);
        return {
            ...store,
            dispatch
        };
    };
}
複製代碼

閱讀完源碼後,我最大的感慨仍是redux的閉包運用,基本到處都是閉包

其中以爲最精妙的部分是applyMiddleware,感受好像不少地方都能用上這個概念,因此我也寫了一篇詳細解析,Redux的中間件applyMiddleware解析

閱讀到這裏的朋友也許也在閱讀源碼,最近我有看一些源碼解析文章,就在思考,如何才能把源碼解析給說清楚,後來想一想,源碼源碼,想必也都是代碼了,靠文字確定是說不清的

最後,祝你們身體健康,工做順利!

歡迎你們關注個人公衆號~

相關文章
相關標籤/搜索