前言
上一篇中,我们介绍了Redux
的一些基础知识,还有基本实现,还介绍了如何实现同步和异步的Action
与ActionCreator
。
如果还没有看上一篇的小伙伴,可以去看看一看我的上一篇帖子。
在这篇帖子里,我将继续上次的demo,讲述如何实现thunk中间件。虽然国内有很多小伙伴也都给出了如何使用React
+ Redux
+ typescript
实现thunk
中间件,但是好像使用到connect
模式的比较少。
在这篇帖子里,我会详细地讲述如何使用thunk中间件实现异步Action
与ActionCreator
。再次重申,本文不涉及到如何实现,对于新手入门来说,如何实现没那么重要,学会使用才是第一步!Lol
本文Demo的Repo地址:
0. 安装thunk
中间件
npm i redux-thunk
复制代码
1. 修改ClockComponent
在这个Demo中,我们基于上一次的Demo,把ClockComponent
从Promise
中间件修改成了thunk
中间件,其实修改的代码很少,主要改了如下几点:
Action
ActionCreator
Reducer
connect
方法的第二个参数mapDispatchToProps
方法Index
中使用的中间件
下面我们对这些修改的点进行逐一的详细分析。
2. 详细分析
2.1 Action
const FETCHING_TIME = 'FETCHING_TIME';
const FETCHED_TIME = 'FETCHED_TIME';
export interface FetchingTimeAction {
type: string
};
export interface FetchedTimeAction {
type: string;
payload: Date;
}
复制代码
ActionType
从4个变成了2个
在上一篇文章里面我们提到过,
Promise
中间件会为我们自动生成3个Action
他们的
Type
分别为:*_PENDING
*_FULFILLED
*_REJECTED
然后,由
Promise
中间件负责将这3个Action
分发到Reducer
中,这个是我们控制不了的。但是对于thunk
中间件来说,一切都是我们可以控制的,包括生成几个中间状态的Action,如何dispatch
,都是我们自己控制的。所以,显而易见的是,我们需要的ActionType
变少了。其实,这也恰恰说明了一个问题,相对于
promise
中间件,thunk
中间件更为灵活,但是需要自己做的事情更多了。promise
中间件虽然代码会稍微多一点,但是大部分的事情,由中间件帮我们做好了。孰优孰劣,大家自己把握。ActionPayload
从Promise<Date>
变成了Date
这一点,也是我觉得
thunk
做的更好一点的事情,Action
摆脱了业务逻辑,变成了一个更纯粹的数据格式,业务逻辑交给了ActionCreator
。为什么说
Promise<Date>
中带有业务逻辑呢,因为,如果返回值带有Promise的话,它的消费端就必然需要对Promise进行处理,如果得到正确结果如何处理,如果失败如何处理,所以我说Promise
对象是贷业务逻辑的。
2.2 ActionCreator
//Action Creator
export function fetchTime(parameter: string): ThunkAction<
// Promise of last dispatched action
Promise<FetchedTimeAction>,
// Data type of the last action
Date,
// The type of the parameter for the nested function
string,
// The type of last action to dispatch.
FetchedTimeAction> {
return async (dispatch: ThunkDispatch<Date, string, AnyAction>)=>{
console.log(`parameter is ${parameter}`);
let fetchingAction:FetchingTimeAction = {type: FETCHING_TIME};
// we can dispatch middle action in the creator.
dispatch(fetchingAction);
let time = await getTimeAsync();
let fetchedTimeAction: FetchedTimeAction = {
type: FETCHED_TIME,
// property name must be 'payload'
payload: time
};
return dispatch(fetchedTimeAction);
};
};
复制代码
这部分是变化最大的部分,相当于重写了,只保留了原有的函数名,所以就没有啥可比性了,我们直接来分析一下变化吧。
这个参数,大家可以忽略掉,这个是我用来做测试的,其实没有什么实际意义。
-
ThunkAction<R, S, E, A>
返回值
我们首先来说这几个泛型的参数类型:- R: 最后一次
dispatch
的Action
类型的Promise
对象
e.g. 我们最后一次要`dispatch`的`action`类型为`FetchedTimeAction`,所以这里填`Promise<FetchedTimeAction>`. 复制代码
- S: 最后一次
dispatch
的Action
类型的Payload
类型
e.g. `FetchedTimeAction`的`Payload`类型为`Date`,所以这里要填`Date`. 复制代码
- E:
ActionCreator
方法的参数类型
e.g. 本例中,这个函数的参数为`string`, 所以这里我填了`string`. 复制代码
- A: 最后一次
dispatch
的Action
的类型
e.g. 本例中,最后一次`dispath`的是`FetchedAction`,所以填这个. 复制代码
- R: 最后一次
-
ActionCreator
的实现
fetchTime
这个方法要返回的类型是ThunkAction<R,S,E,A>
, 第一次看到的时候,说实话,我裂开了,什么鬼?不过,
vscode
的智能感知,实在太NB了,我们看一下!ThunkAction<R,S,E,A> = (dispatch: ThunkDispatch<S,E,A>) => R 复制代码
这说明,我们需要一个
dispatch: ThunkDispatch<S,E,A>
作为参数,返回值是我们那个Promise<A>
就可以了,因为R=Promise<A>
.当然,我看有些小伙伴们会把第一个参数也就是R填成
void
,也可以了,更简单。 -
ActionCreator
中dispatch
我们需要的Action
相信上面的一顿操作,大家都有点蒙圈,说实话,我第一次看的时候,也蒙了半天,不过慢慢的看一两遍,基本上就能明白了,如果还有问题,那么你就记住这是个固定写法,按照我的
Demo
去套你Action
就好了,因为最主要的是我们下面要讲的,这个代理里面的实现。//... console.log(`parameter is ${parameter}`); let fetchingAction:FetchingTimeAction = {type: FETCHING_TIME}; // we can dispatch middle action in the creator. dispatch(fetchingAction); let time = await getTimeAsync(); let fetchedTimeAction: FetchedTimeAction = { type: FETCHED_TIME, // property name must be 'payload' payload: time }; return dispatch(fetchedTimeAction); //... 复制代码
dispatch FetchingTimeAction
Reducer
会根据这个action
修改state
中的isFetching=true
,ux
上可以根据isFetching=true
做相应的处理,比如显示进度条啥的,不过我们的demo
里面比较懒,啥都没做LoL.- 调用
Api
调用了
await getTimeAsync()
, 这一步在真实环境下一般都比较慢,所以这是真正的异步意义所在。 dispatch FetchedTimeAction
当上一步调用完成,我们就可以发送
FetchedTimeAction
,Reducer
根据这个Action
会将state
中的isFetching=false
并且设置succeed=true
,ux
得到new-state
也会做相应的处理。而且很巧妙的是
dispatch(fetchedTimeAction)
正好也返回的是这个Action
,正好能对应上我们之前说的ThunkAction
的要求。
2.3 Reducer
export function clockReducer(state = initState, action: any): ClockState {
let result = initState;
switch (action.type) {
case FETCHING_TIME:
result = {
...state,
isFetching: true
};
break;
case FETCHED_TIME:
result = {
...state,
isFetching: false,
succeed: true,
time: action.payload
};
break;
default:
result = state;
break;
}
return result;
}
复制代码
其实,从某种意义上讲,Reducer
的逻辑没有变化,还是根据ActionType
的不同而生成新的State
,其实严格意义上来讲,变化的只有Action
与ActionCreator
。
2.4 connect
方法的第二个参数mapDispatchToProps
方法
const mapDispatchToProps = (dispatch: ThunkDispatch<any, any, AnyAction>) => ({
fetchTime: (parameter: string) => dispatch(fetchTime(parameter))
});
复制代码
这里之所以变化,是因为,dispatch方法的类型变化了,变成了ThunkDispatch类型。
2.5 Index
中使用的中间件
这里的变化更小,就是将promise
中间件改换成为thunk
中间件。