React 03 :: Redux状态管理(续) — 使用thunk中间件

前言

上一篇中,我们介绍了Redux的一些基础知识,还有基本实现,还介绍了如何实现同步和异步的ActionActionCreator

如果还没有看上一篇的小伙伴,可以去看看一看我的上一篇帖子。

juejin.cn/post/696727…

在这篇帖子里,我将继续上次的demo,讲述如何实现thunk中间件。虽然国内有很多小伙伴也都给出了如何使用React + Redux + typescript实现thunk中间件,但是好像使用到connect模式的比较少。

在这篇帖子里,我会详细地讲述如何使用thunk中间件实现异步ActionActionCreator。再次重申,本文不涉及到如何实现,对于新手入门来说,如何实现没那么重要,学会使用才是第一步!Lol

本文Demo的Repo地址:

gitlab.com/yafeya/reac…

0. 安装thunk中间件

npm i redux-thunk
复制代码

1. 修改ClockComponent

在这个Demo中,我们基于上一次的Demo,把ClockComponentPromise中间件修改成了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;
}
复制代码

image.png

  • ActionType从4个变成了2个

    在上一篇文章里面我们提到过,Promise中间件会为我们自动生成3个Action

    他们的Type分别为:

    • *_PENDING
    • *_FULFILLED
    • *_REJECTED

    然后,由Promise中间件负责将这3个Action分发到Reducer中,这个是我们控制不了的。但是对于thunk中间件来说,一切都是我们可以控制的,包括生成几个中间状态的Action,如何dispatch,都是我们自己控制的。所以,显而易见的是,我们需要的ActionType变少了。

    其实,这也恰恰说明了一个问题,相对于promise中间件,thunk中间件更为灵活,但是需要自己做的事情更多了。promise中间件虽然代码会稍微多一点,但是大部分的事情,由中间件帮我们做好了。孰优孰劣,大家自己把握。

  • ActionPayloadPromise<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: 最后一次dispatchAction类型的Promise对象
    e.g. 我们最后一次要`dispatch`的`action`类型为`FetchedTimeAction`,所以这里填`Promise<FetchedTimeAction>`.
    复制代码
    • S: 最后一次dispatchAction类型的Payload类型
    e.g. `FetchedTimeAction`的`Payload`类型为`Date`,所以这里要填`Date`.
    复制代码
    • E: ActionCreator方法的参数类型
    e.g. 本例中,这个函数的参数为`string`, 所以这里我填了`string`.
    复制代码
    • A: 最后一次dispatchAction的类型
    e.g. 本例中,最后一次`dispath`的是`FetchedAction`,所以填这个.
    复制代码
  • ActionCreator的实现
    fetchTime这个方法要返回的类型是ThunkAction<R,S,E,A>, 第一次看到的时候,说实话,我裂开了,什么鬼?

    不过,vscode的智能感知,实在太NB了,我们看一下!

    image.png

    ThunkAction<R,S,E,A> = (dispatch: ThunkDispatch<S,E,A>) => R
    复制代码

    这说明,我们需要一个dispatch: ThunkDispatch<S,E,A>作为参数,返回值是我们那个Promise<A>就可以了,因为R=Promise<A>.

    当然,我看有些小伙伴们会把第一个参数也就是R填成void,也可以了,更简单。

  • ActionCreatordispatch我们需要的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=trueux上可以根据isFetching=true做相应的处理,比如显示进度条啥的,不过我们的demo里面比较懒,啥都没做LoL.

    • 调用Api

      调用了await getTimeAsync(), 这一步在真实环境下一般都比较慢,所以这是真正的异步意义所在。

    • dispatch FetchedTimeAction

      当上一步调用完成,我们就可以发送FetchedTimeActionReducer根据这个Action会将state中的isFetching=false并且设置succeed=trueux得到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,其实严格意义上来讲,变化的只有ActionActionCreator

2.4 connect方法的第二个参数mapDispatchToProps方法

const mapDispatchToProps = (dispatch: ThunkDispatch<any, any, AnyAction>) => ({
    fetchTime: (parameter: string) => dispatch(fetchTime(parameter))
});
复制代码

image.png
这里之所以变化,是因为,dispatch方法的类型变化了,变成了ThunkDispatch类型。

2.5 Index中使用的中间件

image.png

这里的变化更小,就是将promise中间件改换成为thunk中间件。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享