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 类型验证,而后就没啥了。。。

最后,日了狗以为事情并不简单。

相关文章
相关标签/搜索