redux 源碼分析

Redux is a predictable state container for JavaScript apps.

redux 三大原則

單一數據源 Single source of truth:整個應用的 state 被儲存在一棵 object tree 中,而且這個 object tree 只存在於惟一一個 store 中。redux

state 只讀 State is read-only:唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象。這樣確保了視圖和網絡請求都不能直接修改 state,相反它們只能表達想要修改的意圖。數組

使用純函數來執行修改 Changes are made with pure functions:爲了描述 action 如何改變 state tree ,你須要編寫 reducers。Reducer 只是一些純函數,它接收先前的 state 和 action,並返回新的 state。網絡

redux 一些概念

Action: 是把數據傳到 store 的惟一手段,表達修改 state 的意圖。它是 store 數據的惟一來源。通常經過 store.dispatch() 將 action 傳到 store。session

{
        type:types.RECEIVE_CASES,
        cases
    }

Action Creator:是一個建立 action 的純函數。閉包

export function receiveCases(cases) {
      return {
        type:types.RECEIVE_CASES,
        cases
      }
    }

Reducer:是一個純函數,接收舊的 state 和 action,一般包含了switch結構,根據dispatch傳來的state和type來生成新的state。app

Store:構建順序:框架

reducers => 'combineReducers'
                    => rootReducer => 'createStore' (+ 'applyMiddleware')
                                                    |
                                                   ===> **Store**

createStore.js

  • redux.createStore(reducer, initialState) 傳入了reducer、initialState,並返回一個store對象。ide

  • store對象對外暴露了dispatch、getState、subscribe方法函數

  • store對象經過getState() 獲取內部狀態ui

  • initialState爲 store 的初始狀態,若是不傳則爲undefined

  • store對象經過reducer來修改內部狀態

  • store對象建立的時候,內部會主動調用dispatch({ type: ActionTypes.INIT });來對內部狀態進行初始化。經過斷點或者日誌打印就能夠看到,store對象建立的同時,reducer就會被調用進行初始化。

/**
     * Creates a Redux store that holds the state tree.
     * The only way to change the data in the store is to call `dispatch()` on it.
     *
     * There should only be a single store in your app. To specify how different
     * parts of the state tree respond to actions, you may combine several reducers
     * into a single reducer function by using `combineReducers`.
     *
     * @param {Function} reducer A function that returns the next state tree, given
     * the current state tree and the action to handle.
     * --- reducer 是 Function,用於構建 state 樹
     *
     * @param {any} [preloadedState] The initial state. You may optionally specify it
     * to hydrate the state from the server in universal apps, or to restore a
     * previously serialized user session.
     * If you use `combineReducers` to produce the root reducer function, this must be
     * an object with the same shape as `combineReducers` keys.
     * --- preloadedState 初始化 state 樹
     *
     * @param {Function} enhancer The store enhancer. You may optionally specify it
     * to enhance the store with third-party capabilities such as middleware,
     * time travel, persistence, etc. The only store enhancer that ships with Redux
     * is `applyMiddleware()`.
     * --- enhancer 至關於 AOP 插件
     *
     * @returns {Store} A Redux store that lets you read the state, dispatch actions
     * and subscribe to changes.
     * --- 最終返回 Store,能獲取 state、分發action、訂閱變化
     */
    export default function createStore(reducer, preloadedState, enhancer) {
      ... // --- 校驗參數,及執行 enhancer
    
      var currentReducer = reducer
      var currentState = preloadedState // --- state 樹
      var currentListeners = [] // --- 註冊的監聽器列表,實時處理 dispatch 事件
      var nextListeners = currentListeners // --- 註冊的監聽器列表,實時接收 subscribe 事件
      var isDispatching = false // --- 若是 reducer 正在執行,會拋出異常
    
      // --- 將 nextListeners 作爲 currentListeners 的副本
      function ensureCanMutateNextListeners() {
        if (nextListeners === currentListeners) {
          nextListeners = currentListeners.slice()
        }
      }
    
      // --- 返回 state
      function getState() {
        ... // --- 校驗
    
        return currentState
      }
    
      // --- 註冊監聽器
      // 很常見的監聽函數添加方式,當store.dispatch 的時候被調用
      // store.subscribe(listener) 返回一個方法(unscribe),能夠用來取消監聽
      function subscribe(listener) {
        listeners.push(listener);
        var isSubscribed = true;
    
        return function unsubscribe() {
          if (!isSubscribed) {
            return;
          }
    
          isSubscribed = false;
          var index = listeners.indexOf(listener);
          listeners.splice(index, 1);
        };
      }
      
      // --- 分發 action,修改 state 的惟一方式
      function dispatch(action) {
        
        // 如下狀況會報錯
        // 1. 傳入的action不是一個對象
        // 2. 傳入的action是個對象,可是action.type 是undefined
        try {
          isDispatching = true
          // 就是這一句啦, 將 currentState 設置爲 reducer(currentState, action) 返回的值
          currentState = currentReducer(currentState, action) // 執行 reducer,state 被更新
        } finally {
          isDispatching = false
        }
    
        // --- 若是有監聽函數,就順序調用監聽器方法
        //listeners.slice().forEach(listener => listener());
        var listeners = currentListeners = nextListeners
        for (var i = 0; i < listeners.length; i++) {
          listeners[i]()
        }
    
        // --- 返回結果仍是 action
        return action
      }
    
      // --- 替換 reducer,用於熱替換、按需加載等場景
      function replaceReducer(nextReducer) {
        ... // --- 校驗
    
        currentReducer = nextReducer
        dispatch({ type: ActionTypes.INIT })
      }
    
      function observable() {
        // --- 略
      }
    
      // --- 建立時初始化應用狀態
      // redux.createStore(reducer, initialState) 的時候, 內部會 本身調用 dispatch({ type: ActionTypes.INIT });
      // 來完成state的初始化
      dispatch({ type: ActionTypes.INIT })
    
      return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable
      }
    }

combineReducers.js

function TodoReducer(state, action) {}
    function FilterReducer(state, action) {}
    
    var finalReducers = redux.combineReducers({
        todos: TodoReducer,
        filter: FilterReducer
    });

redux.combineReducers(reducerMap) 用來合成多個reducer,其實就是把全部輸入的reducer閉包封裝,返回一個combination函數,這個函數會接受state和action,依次調用封裝的reducer,分發state和action,來生成新的state

  • combineReducers(reducerMap) 傳入一個對象,並返回一個全新的reducer。調用方式跟跟普通的reducer同樣,也是傳入state、action。

  • 經過combineReducers,對 store 的狀態state進行拆分,

  • reducerMap的key,就是 state 的key,而 調用對應的reducer返回的值,則是這個key對應的值。如上面的例子,state.todos == TodoReducer(state, action)

  • redux.createStore(finalReducers, initialState) 調用時,一樣會對 state 進行初始化。這個初始化跟經過普通的reducer進行初始化沒多大區別。舉例來講,若是 initialState.todos = undefined,那麼 TodoReducer(state, action) 初始傳入的state就是undefined;若是initialState.todos = [],那麼 TodoReducer(state, action) 初始傳入的state就是[];

  • store.dispatch(action),finalReducers 裏面,會遍歷整個reducerMap,依次調用每一個reducer,並將每一個reducer返回的子state賦給state對應的key。

/**
     * Turns an object whose values are different reducer functions, into a single
     * reducer function. It will call every child reducer, and gather their results
     * into a single state object, whose keys correspond to the keys of the passed
     * reducer functions.
     *
     * @param {Object} reducers An object whose values correspond to different
     * reducer functions that need to be combined into one. One handy way to obtain
     * it is to use ES6 `import * as reducers` syntax. The reducers may never return
     * undefined for any action. Instead, they should return their initial state
     * if the state passed to them was undefined, and the current state for any
     * unrecognized action.
     *
     * @returns {Function} A reducer function that invokes every reducer inside the
     * passed object, and builds a state object with the same shape.
     */
    export default function combineReducers(reducers) {
      // --- 過濾 reducer
      var reducerKeys = Object.keys(reducers)
      var finalReducers = {}
      for (var i = 0; i < reducerKeys.length; i++) {
        var key = reducerKeys[i]
    
        if (typeof reducers[key] === 'function') {
          finalReducers[key] = reducers[key]
        }
      }
      var finalReducerKeys = Object.keys(finalReducers)
    
      // --- 返回 combination 函數
      return function combination(state = {}, action) {
        // --- 校驗
    
        // --- 根據 reducer key 及執行結果構造 state 樹
        var hasChanged = false
        var nextState = {}
        for (var i = 0; i < finalReducerKeys.length; i++) {
          var key = finalReducerKeys[i]
          var reducer = finalReducers[key]
          var previousStateForKey = state[key]
          var nextStateForKey = reducer(previousStateForKey, action) // --- 執行 reducer
          // --- 校驗
          nextState[key] = nextStateForKey
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        return hasChanged ? nextState : state
      }
    }

applyMiddleware.js

applyMiddleware 傳入 middleware 鏈,並返回應用這些 middleware 的 store enhancer

export default function applyMiddleware(...middlewares) {
      return (next) => (reducer, initialState) => {
        // 內部先建立一個store (至關於直接調用 Redux.createStore(reducer, initialState))
        var store = next(reducer, initialState);
        // 保存最初始的store.dispatch
        var dispatch = store.dispatch;
        var chain = [];
    
        var middlewareAPI = {
          getState: store.getState,
          // 最後面, dispatch 被覆蓋, 變成包裝後的 dispatch 方法
          dispatch: (action) => dispatch(action)
        };
        // 返回一個數組
        // 貼個例子在這裏作參考,redux-thunk
        // function thunkMiddleware(store) {
        //  var dispatch = store.dispatch;
        //  var getState = store.getState;
        //
        //  這裏的next其實就是dispatch
        //  return function (next) {
        //    return function (action) {
        //      return typeof action === 'function' ? action(dispatch, getState) : next(action);
        //    };
        //  };
        //}
        /*
          chain 是個數組, 參考上面的 middlleware (redux-thunk),能夠看到,chain的每一個元素爲以下形式的function
          而且, 傳入的 store.getState 爲原始的 store.getState,而 dispatch則是包裝後的 dispatch(不是原始的store.dispatch)
          彷佛是爲了確保, 在每一個middleware裏調用 dispatch(action), 最終都是 用原始的 store.dispatch(action)
          避免 store.dispatch 被覆蓋, 致使middleware 順序調用的過程當中, store.dispatch的值變化 --> store.dispatch 返回的值可能會有不一樣
          違背 redux 的設計理念
    
          這裏的 next 則爲 原始的 store.dispatch (見下面 compose(...chain)(store.dispatch) )
          function (next) {
            return function (action) {
    
            }
          }
         */
        chain = middlewares.map(middleware => middleware(middlewareAPI));
    
        // compose(...chain)(store.dispatch) 返回了一個function
        // 僞代碼以下,
        // function (action) {
        //   middleware(store)(store.dispatch);
        // }
        dispatch = compose(...chain)(store.dispatch);  // 從右到左, middleware1( middleware2( middleware3(dispatch) ) )
    
        // 因而,最終調用 applyMiddleware(...middlewares)(Redux.createStore)
        // 返回的 store, getState,subscribe 方法都是原始的那個 store.getState, store.subscribe
        // 至於dispatch是封裝過的
        return {
          ...store,
          dispatch
        };
      };
    }
export default function applyMiddleware(...middlewares) {
      // --- 傳入 createStore,返回加強版的 store
      return (createStore) => (reducer, preloadedState, enhancer) => {
        var store = createStore(reducer, preloadedState, enhancer)
        var dispatch = store.dispatch
        var chain = []
    
        // --- middleware 中的參數
        var middlewareAPI = {
          getState: store.getState,
          dispatch: (action) => dispatch(action)
        }
    
        // --- 給 middleware 傳參
        chain = middlewares.map(middleware => middleware(middlewareAPI))
    
        // --- 組合 chain,傳入原始的 dispatch
        dispatch = compose(...chain)(store.dispatch)
    
        return {
          ...store, // --- 保留 subscribe, getState, replaceReducer, [$$observable]: observable
          dispatch // --- 用新 dispatch 覆蓋,以後調用 dispatch 就會觸發 chain 內的 middleware 鏈式執行
        }
      }
    }

Provider.js

React-Redux是用在鏈接React和Redux上的。若是你想同時用這兩個框架,那麼React-Redux基本就是必須的了。

export default class Provider extends Component {
      getChildContext() {
        // 將其聲明爲 context 的屬性之一
        return { store: this.store }
      }
    
      constructor(props, context) {
        super(props, context)
        // 接收 redux 的 store 做爲 props
        this.store = props.store
      }
    
      render() {
        return Children.only(this.props.children)
      }
    }
    
    Provider.propTypes = {
      store: storeShape.isRequired,
      children: PropTypes.element.isRequired
    }
    Provider.childContextTypes = {
      store: storeShape.isRequired
    }
相關文章
相關標籤/搜索