redux-thunk 之謎

若是你在用 redux 的話,那你估計也會用到 redux-thunk 這個庫。區別於做者的說法,如下是日了狗的想法。編程

thunk 求值

日了狗說:「你可能得先了解下 thunk 。」json

編程語言早期,計算機學家在研究如何編譯時,對「求值策略」產生了爭執。舉個栗子:redux

const x = 1;

function foo(num) {
  return num * 2;
}

foo(x + 3);
複製代碼

x + 3 該在什麼時候計算?引起了兩種意見:app

  • 傳值調用,在進入 foo 之間計算,等同於 foo(4)
  • 傳名調用,只在用到時求值,等同於(x + 3) * 2

兩種計算方式各有利弊,傳值調用相對簡單,但有可能根本沒用到,好比:異步

function bar(a, b) {
  return b + 2;
}

bar(1 + 2 * (3 - x) / x -4, x);
複製代碼

定義的 bar 函數體內沒有用到 a ,根本無需計算,傳值調用可能會形成性能損失。因此有些計算機學家更傾向於傳名調用async

thunk 求值就是基於傳名調用的編譯器實現。編程語言

const a = 1;
const thunk = function thunk(x) {
  return x + 3;
}

function foo(thunk) {
  return thunk() * 2;
}
複製代碼

在 JavaScript 語言中,thunk 函數略有不一樣。也叫函數「柯里化」,一種函數式編程的概念,將多參數轉化爲單參數形式函數式編程

function foo(var1, var2) {
  return var1 + var2;
}

function thunk(var1) {
  return function (var2) {
    return var1 + var2;
  };
}
// 看最多的是 ES6 的騷寫法
const thunk = var1 => var2 => var1 + var2;

// 實際上仍是傳值調用
foo(1, 2) === thunk(1)(2);
複製代碼

日了狗說:「好的。咱們如今開始回到 redux-thunk 。」函數

爲何要用

當你在 redux 中觸發 state 變化時,你確定會這樣 dispatch(action)。處理同步代碼固然沒問題,可是異步代碼呢?好比說異步請求,emmmmm...跟着官方的代碼走是這樣的:性能

function success(data) {
  return {
    type: 'SUCCESS',
    data,
  };
}

function asyncFunc(payload) {
  return dispatch => {
    fetch(url, payload)
      .then(res => res.json())
      .then(data => dispatch(success(data)))
  };
}

dispatch(asyncFunc({}));
複製代碼

若是你沒用 redux-thunk 的話,你應該會收到一條錯誤提示。這是由於 redux 裏作了對 action 的校驗:

// 必需要是個包含 type 屬性的純對象
// 而 asyncFunc 返回的是個 Promise
if (!isPlainObject(action)) {
  throw new Error(
    'Actions must be plain objects. ' +
      'Use custom middleware for async actions.'
  )
}

if (typeof action.type === 'undefined') {
  throw new Error(
    'Actions may not have an undefined "type" property. ' +
      'Have you misspelled a constant?'
  )
}
複製代碼

聰明的你會想到另外一種解決辦法。執行 Promise,dispatch 對象:

// store.js
const store = createStore();

// action.js
function success(data) {
  return {
    type: 'SUCCESS',
    data,
  };
}

// index.js
fetch(url, data)
  .then()
  .then(res => /* import store from store.js */ store.dispatch(success(res)));
複製代碼

福兮禍所依,這樣子的寫法有幾個問題:

  1. 寫法不規範。createAction 不統一
  2. 保持對 store 的引用。污染全局、修改此對象均可能形成影響
  3. 重複代碼。同一個請求多處寫

日了狗說:「ok,如今咱們來看看 redux-thunk 作了什麼事。」

作了什麼事

直接看源碼。只有區區不到 15 行的代碼:

// redux-thunk 功能代碼
const thunk = ({ dispatch, getState }) => next => action => {
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }

  return next(action);
}
複製代碼

光看這裏代碼一臉懵。可能你們一直在糾結 next 是什麼,action 又是什麼。那不妨多糾結一下,看看 redux 的中間件怎麼應用的:

// 核心代碼
function applyMiddleware(...middlewares) {
  const middlewareAPI = {
    getState: store.getState,
    dispatch: (...args) => dispatch(...args)
  }
  const chain = middlewares.map(middleware => middleware(middlewareAPI))

  dispatch = compose(...chain)(store.dispatch)
}
複製代碼

主要作了兩件事:

  1. dispatch 注入中間件
  2. 將全部的中間件 compose 成一個 dispatch

redux 中的 compose 函數頗有意思,將多個函數整合成一個,按順序同步執行,上一個函數的返回值爲下一個函數的入參。詳情可看源碼

Tip: 若是看源碼不容易理解的話,能夠嘗試看下測試用例

瞭解了 applyMiddleware 後,再應用 redux-thunk,你就會發現本來的 dispatch 函數被改變了。在 action 觸發之間會先執行一遍 redux-thunk 功能代碼,判斷傳入的 action 是否爲函數類型,若是是,則注入 dispatch,執行此函數

簡而言之,redux-thunk 直接讓 dispatch 跳過了 action 類型驗證,而後就沒啥了。。。

最後,日了狗以爲事情並不簡單。

相關文章
相關標籤/搜索