redux源碼解讀(一)

redux 的源碼雖然代碼量並很少(除去註釋大概300行吧)。可是,由於函數式編程的思想在裏面體現得淋漓盡致,理解起來並不太容易,因此準備使用三篇文章來分析。html

  • 第一篇,主要研究 redux 的核心思想和實現,並用100多行的代碼實現了其核心功能,相信看完以後,你會徹底理解 redux的核心。這裏甩掉 combindReducersapplyMiddleware,不會涉及很高深的柯里化、高階、歸併的思想,可是須要你對閉包有必定的理解。其實,redux 源碼自己並不可怕,可怕的是網上太多文章把他和函數式放在一塊兒來分析(裝逼)了!!!嚇得咱們一看到就想跑了。
  • 第二篇, 理解了 redux 的核心以後, 咱們會分析 reducers 合併(即 combindReducers)的實現。
  • 第三篇會分析加強器(即 applyMiddleware)的實現,這是最體現函數式風格的地方,並實現一個處理異步請求的 promise 中間件。

在解讀 redux 源碼以前,咱們首先要弄清楚一個問題,就是 reduxreact-redux 不是同一個東東。 react-redux 是爲 react 而定製的,主要是提供 Provider 組件和 connect 方法,方便於咱們把 reduxreact組件 綁定起來。可是, redux 是沒有限制說必定要跟 react 一塊兒使用的。本文只介紹 redux ,不涉及 react 或者 react-redux 。由於我以爲,若是把 reduxreact 放在一塊兒討論,反而會加深了理解的複雜度,分散了咱們的注意力,從而影響咱們分析源碼進度。如今要分析 redux 源碼,那就只專一於 redux,甩開 react , 就連後面的測試例子,也不要引入 react,就簡單的使用原生html和js測試一下就OK了。react

什麼是 redux 呢?, 這裏也不展開介紹了。就簡單的回顧一下 redux 的具體用法:git

  1. 定義一個 reducer 函數
  2. 調用 redux.createStore(reducer) 方法建立 store 實例
  3. 經過store.subscribe(callback) 方法訂閱回調事件(即狀態變化時會觸發回調函數callback)
  4. 經過用戶交互(如點擊事件)調用 store.dispatch(action), 改變 store 的狀態

可能用些朋友會說,我歷來沒有用過 store.subscribe啊,那是由於你使用了 react-redux, 在 connet() 的時候幫你作了這一步。好吧,說好了不扯 react的。那下面咱們就就一步步的來實現 redux 的核心功能吧。github

首先來看一下 createStore, 咱們平時的用法以下:編程

const store = createStore(reducer, preloadedState, enhance)

能夠接受3個參數,第一個是自定義的reducer函數, 第二個是初始狀態,第三個是加強器(即 applyMiddleware()返回的東西),由於前面已經說過了,這裏咱們不會涉及到 applyMiddleware,因此,咱們的 createStore只接收2個參數,以下:redux

function createStore(reducer, preloadedState) {
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }
  
  // 定義一些變量,後面幾乎全部的方法都會用到,這就是閉包的力量!
  let currentState = preloadedState // state
  let listeners = [] // 訂閱事件列表
  let isDispatching = false // 是否正在執行reducer
}

createStore 參數和可能會用到的變量定義好了,咱們須要實現三個函數,分別是 store.getStatestore.subscribestore.dispatch數組

首先來實現 store.getState 方法,這個方法沒有好說的,就是把閉包裏面的 currentState 返回出去就好了,代碼以下:promise

function createStore(reducer, preloadedState) {
  // 省略和上面重複的代碼
  
  // 獲取state
  function getState() {
    // 若是正在執行reducer,則拋出異常
    if (isDispatching) {
      throw new Error('You may not call store.getState() while the reducer is executing. ')
    }
    return currentState;
  }
}

接着咱們來實現 store.subscribe。這個方法是用來添加訂閱回調函數的。首先要判斷傳進來的參數是否是函數類型,而後,把他它push到回調隊列(數組)裏面。由於可能後面須要把這個回調取消掉,因此還要返回一個方法給外部調用取消,實現代碼以下:閉包

function createStore(reducer, preloadedState) {
  // 省略和上面重複的代碼
  
  // 添加訂閱事件
  function subscribe(listener) {
    if(typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }

    let isSubscribed = true;
    listeners.push(listener);

    // 返回一個取消訂閱事件的函數
    return function unsubscribe() {
      if(!isSubscribed) {
        return;
      }

      if(isDispatching) {
        throw new Error('You may not unsubscribe from a store listener while the reducer is executing. ');
      }

      isSubscribed = false;

      const index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    }
  }
}

最後,咱們再來看一下 store.dispatch 方法的實現。 dispatch 接受的參數類型是一個 action 。咱們來回顧一下 action是什麼鬼?他要求是一個原生對象,並且必需要有 type 屬性,還有可能有 payload 屬性。以下是咱們的一個用法 :app

store.dispatch({
  type: 'ADD_SHOPPING',
  payload: 1
})

調用store.dispatch(action), 它的返回值也是 action。下面代碼是 store.dispatch()的實現:

function createStore(reducer, preloadedState) {
  // 省略和上面重複的代碼
  
  function dispatch(action) {
    // 若是action不是原生對象,則拋出異常
    // 由於咱們期待的action結構爲"{type: 'xxx', payload: 'xxx'}"的原生對象
    if(Object.prototype.toString.call(action, null) !== '[object Object]') {
      throw new Error('Actions must be plain objects. ');
    }

    if(typeof action.type === 'undefined') {
      throw new Error('Actions may not have an undefined "type" property. ')
    }

    if(isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    // 開始調用reducer獲取新狀態。由於可能會出錯須要用try-catch
    // 而且無論成功失敗,執行完畢後都要設置isDispatching=true
    try {
      isDispatching = true;
      currentState = reducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    // 遍歷全部經過store.subscribe()綁定的的訂閱事件,並調用他們
    listeners.forEach((listener) => {
      listener();
    })

    return action;
  }
}

關於 redux 的分析就寫到這裏的了。下面是前面分析的代碼整合到了一塊兒。

function createStore(reducer, preloadedState) {
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  let currentState = preloadedState // state
  let listeners = [] // 訂閱事件列表
  let isDispatching = false // 是否正在執行reducer

  function getState() {
    // 若是正在執行reducer,則拋出異常
    if (isDispatching) {
      throw new Error('You may not call store.getState() while the reducer is executing. ')
    }
    return currentState;
  }

  // 添加訂閱事件
  function subscribe(listener) {
    if(typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }

    let isSubscribed = true;
    listeners.push(listener);

    // 返回一個取消訂閱事件的函數
    return function unsubscribe() {
      if(!isSubscribed) {
        return;
      }

      if(isDispatching) {
        throw new Error('You may not unsubscribe from a store listener while the reducer is executing. ');
      }

      isSubscribed = false;

      const index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    }
  }

  function dispatch(action) {
    // 若是action不是原生對象,則拋出異常
    // 由於咱們期待的action結構爲"{type: 'xxx', payload: 'xxx'}"的原生對象
    if(!isPlainObject(action)) {
      throw new Error('Actions must be plain objects. ');
    }

    if(typeof action.type === 'undefined') {
      throw new Error('Actions may not have an undefined "type" property. ')
    }

    if(isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    // 開始調用reducer獲取新狀態。由於可能會出錯須要用try-catch
    // 而且無論成功失敗,執行完畢後都要設置isDispatching=true
    try {
      isDispatching = true;
      currentState = reducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    // 遍歷全部經過store.subscribe()綁定的的訂閱事件,並調用他們
    listeners.forEach((listener) => {
      listener();
    })

    return action;
  }

  // 將getState, subscribe, dispatch這三個方法暴露出去
  // 建立了store實例以後,能夠store.getState()、store.subscripbe()...
  return {
    getState,
    subscribe,
    dispatch
  }
}

完整的代碼和測試例子,能夠到個人github下載 點擊進入simplest-redux 。若是以爲我分析得還不是太清楚的,建議把github上的代碼clone下來,本身多看幾遍,並在demo中運行調試幾下就會明白的了。

相關文章
相關標籤/搜索