如果你在用 redux
的话,那你估计也会用到 redux-thunk
这个库。区别于作者的说法,以下是日了狗的想法。
thunk
求值
日了狗说:“你可能得先了解下
thunk
。”
编程语言早期,计算机学家在研究如何编译时,对“求值策略”产生了争执。举个栗子:
const x = 1;
function foo(num) {
return num * 2;
}
foo(x + 3);
x + 3
该在何时计算?引发了两种意见:
- 传值调用,在进入
foo
之间计算,等同于foo(4)
- 传名调用,只在用到时求值,等同于
(x + 3) * 2
两种计算方式各有利弊,传值调用相对简单,但有可能根本没用到,比如:
function bar(a, b) {
return b + 2;
}
bar(1 + 2 * (3 - x) / x -4, x);
定义的 bar
函数体内没有用到 a
,根本无需计算,传值调用可能会造成性能损失。所以有些计算机学家更倾向于传名调用
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)));
福兮祸所依,这样子的写法有几个问题:
- 写法不规范。
createAction
不统一 - 保持对
store
的引用。污染全局、修改此对象都可能造成影响 - 重复代码。同一个请求多处写
日了狗说:“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)
}
主要做了两件事:
- 将
dispatch
注入中间件 - 将所有的中间件 compose 成一个
dispatch
redux 中的 compose
函数很有意思,将多个函数整合成一个,按顺序同步执行,上一个函数的返回值为下一个函数的入参。详情可看源码
Tip: 如果看源码不容易理解的话,可以尝试看下测试用例
了解了 applyMiddleware
后,再应用 redux-thunk
,你就会发现原本的 dispatch
函数被改变了。在 action
触发之间会先执行一遍 redux-thunk
功能代码,判断传入的 action
是否为函数类型,如果是,则注入 dispatch
,执行此函数
简而言之,redux-thunk
直接让 dispatch
跳过了 action
类型验证,然后就没啥了。。。
最后,日了狗觉得事情并不简单。