如何使用 redux-thunk 和 TypeScript 调度 ThunkAction

Posted

技术标签:

【中文标题】如何使用 redux-thunk 和 TypeScript 调度 ThunkAction【英文标题】:How to dispatch ThunkAction with redux-thunk and TypeScript 【发布时间】:2021-02-27 15:25:45 【问题描述】:

我在使用 Typescript 调度 redux-thunk 操作时遇到问题。

import  AnyAction, applyMiddleware, createStore  from 'redux'
import thunk,  ThunkAction  from 'redux-thunk'

interface State 
  counter: number


const initialState: State = 
  counter: 10


function reducer(state = initialState, action: AnyAction) 
  switch (action.type) 
    case 'increment':
      return  counter: action.payload 
    default:
      return state
  


function increment(): ThunkAction<void, State, unknown, AnyAction> 
  return async function (dispatch) 
    dispatch(
      type: 'increment',
      payload: 20
    )
  


const store = createStore(reducer, applyMiddleware(thunk))

store.dispatch(increment())

这是我收到的错误:

Argument of type 'ThunkAction<void, State, unknown, AnyAction>' is not assignable to parameter of type 'AnyAction'.
  Property 'type' is missing in type 'ThunkAction<void, State, unknown, AnyAction>' but required in type 'AnyAction'.

我尝试了多种不同的操作类型,例如自定义界面、操作等,但没有任何效果。

【问题讨论】:

【参考方案1】:

默认的dispatch 类型不知道thunk,因为“base redux”类型不是很强大。因此,您必须手动将其转换为 ThunkDispatch:

(store.dispatch as ThunkDispatch<State, unknown, AnyAction>)(increment())

就像 PSA:您在此处编写的 redux 类型(带有手写动作、动作类型、switch-case 语句和 reducer 中的不可变逻辑的 vanilla redux)不再是“官方推荐的写作方法”还原。 请查看redux toolkit 并最好关注官方最新的redux tutorials,因为您很可能关注的是一个非常过时的。

Redux Toolkit 也是一个很多更容易使用的工具,特别是 TypeScript(如果你使用它,store.dispatch 将有正确的类型;))

【讨论】:

这行得通。我只想指出我使用的是没有 React 的 redux,而且据我所知 redux-toolkit 假设你使用的是 React。 @jm18457 它没有。它完全与框架无关。【参考方案2】:

对于那些在使用 thunk 和 hooks 时难以处理调度功能的人来说,这只是一个建议。

这是一个我正在做什么来管理身份验证状态的示例,从 graphql 服务器获取数据。定义调度类型type IAppDispatch = ThunkDispatch&lt;IAppState, any, IAppActions&gt;;时神奇来了

store.ts

import  applyMiddleware, combineReducers, compose, createStore  from "redux";
import thunkMiddleware,  ThunkDispatch, ThunkMiddleware  from "redux-thunk";
import  authReducer  from "./reducers/authReducers";
import  IAuthActions  from "./types/authTypes";

const composeEnhancers =
    process.env.NODE_ENV === "development"
        ? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
        : compose;

const rootReducer = combineReducers(
    authReducer,
);

type IAppActions = IAuthActions; <-- merge here other actions
type IAppState = ReturnType<typeof rootReducer>;
type IAppDispatch = ThunkDispatch<IAppState, any, IAppActions>; <--here is the magic

const reduxStore = createStore(
    rootReducer,
    composeEnhancers(
        applyMiddleware<IAppDispatch, any>(
            thunkMiddleware as ThunkMiddleware<IAppState, IAppActions, any>
        )
    )
);

export  reduxStore, IAppState, IAppDispatch, IAppActions ;

authActions(动作创建者和调度 thunk 动作)

import  Dispatch  from "redux";
import 
    loginMutation,
    logoutMutation,
 from "../../components/DataComponents/Authentification/fetchAuthentification";
import  GqlSessionUser  from "../../components/DataComponents/generatedTypes";
import 
    IAuthActions,
    IAuthErrorAction,
    IAuthLoadingAction,
    IAuthLoginAction,
    IAuthLogoutAction,
 from "../types/authTypes";

const authLogin = (appUserId: GqlSessionUser): IAuthLoginAction => 
    return 
        type: "AUTH_LOGIN",
        payload: 
            appUserId,
        ,
    ;
;

const authLogout = (): IAuthLogoutAction => 
    return 
        type: "AUTH_LOGOUT",
    ;
;

const authLoadingAction = (isLoading: boolean): IAuthLoadingAction => 
    return 
        type: "AUTH_LOADING",
        payload: 
            isLoading,
        ,
    ;
;

const authErrorAction = (errorMessage: string): IAuthErrorAction => 
    return 
        type: "AUTH_ERROR",
        payload: 
            errorMessage,
        ,
    ;
;


const authLoginAction = (idOrEmail: string) => 
    return async (dispatch: Dispatch<IAuthActions>) => 
        dispatch(authLoadingAction(true));
        const  data, errors  = await loginMutation(idOrEmail); <--fetch data from GraphQl

        if (data) 
            dispatch(authLogin(data.login.data[0]));
        
        if (errors) 
            dispatch(authErrorAction(errors[0].message));
        

        dispatch(authLoadingAction(false));

        return true;
    ;
;

const authLogoutAction = () => 
    return async (dispatch: Dispatch<IAuthActions>) => 
        dispatch(authLoadingAction(true));
        await logoutMutation(); <--fetch data from GraphQl
        dispatch(authLogout());
        dispatch(authLoadingAction(false));

        return true;
    ;
;

export 
    authLoginAction,
    authLogoutAction,
    authLoadingAction,
    authErrorAction,
;

使用状态并通过 useDispatch 分派异步操作的组件示例

请不要将 dispatch 键入为 IAppDispatch,尽管它是从 react-redux 导入的

    import React from "react";
import  useDispatch, useSelector  from "react-redux";
import 
    authLoginAction,
    authLogoutAction,
 from "../../../stateManagement/actions/authActions";
import  IAppDispatch, IAppState  from "../../../stateManagement/reduxStore";
import Button from "../../Button";

const Authentification: React.FC = (): JSX.Element => 
        const dispatch: IAppDispatch = useDispatch(); <--typing here avoid "type missing" error

        const isAuth = useSelector<IAppState>((state) => state.authReducer.isAuth);
    
        const authenticate = async (idOrEmail: string): Promise<void> => 
            if (!isAuth) 
                dispatch(authLoginAction(idOrEmail)); <--dispatch async action through thunk
             else 
                dispatch(authLogoutAction()); <--dispatch async action through thunk
            
        ;
    
        return (
            <Button
                style=
                    backgroundColor: "inherit",
                    color: "#FFFF",
                
                onClick=() => authenticate("jerome_altariba@carrefour.com")
            >
                isAuth && <p>Logout</p>
                !isAuth && <p>Login</p>
            </Button>
        );
    ;
    export  Authentification ;

【讨论】:

【参考方案3】:

我最近在尝试从 HOC 连接升级我的应用程序以使用挂钩时遇到了这个问题。由于我没有使用redux-toolkit(出于历史原因),因此如何正确使用打字稿有点令人困惑。该解决方案基于一些带有打字稿模板的旧create-react-app。我已经完成了似乎正在工作的事情:

store.ts

import  AnyAction  from 'redux';
import  TypedUseSelectorHook, useDispatch, useSelector  from 'react-redux';
import  ThunkDispatch  from 'redux-thunk';

export interface ApplicationState  
    sliceName: SliceType
    // other store slices


export interface AppThunkAction<TAction> 
    (dispatch: (action: TAction) => void, getState: () => ApplicationState): void;


export const useStoreSelector: TypedUseSelectorHook<ApplicationState> = useSelector;
export const useStoreDispatch = () => useDispatch<ThunkDispatch<ApplicationState, unknown, AnyAction>>();

storeSlice.ts

import  AppThunkAction  from './index';

export interface StandardReduxAction  type: 'STANDARD_REDUX' 
export interface ReduxThunkAction  type: 'REDUX_THUNK', data: unknown 

interface SliceNameActions 
    standardRedux: (show: boolean) => StandardReduxAction;
    reduxThunk: () => AppThunkAction<ReduxThunkAction>;


export const SliceNameActionCreators: SliceNameActions = 

    standardRedux: (): StandardReduxAction =>  type: StandardReduxAction ;

    reduxThunk: (): AppThunkAction<ReduxThunkAction> => async (dispatch, getState): Promise<void> => 
                    
        let response = await asyncCallSomewhere();
        dispatch( type: ReduxThunkAction, data: response );
    

anyComponent.tsx

import  useStoreDispatch  from 'store';
import  SliceNameActionCreators  from 'storeSlice';

const dispatch = useStoreDispatch();

const dispatchStandardRedux = () => dispatch(SliceNameActionCreators.standardRedux());
const dispatchReduxThunk = () => dispatch(SliceNameActionCreators.reduxThunk());

目前推荐使用打字稿设置React-Redux的方法是使用Redux Toolkit,可以在here找到指南。

【讨论】:

以上是关于如何使用 redux-thunk 和 TypeScript 调度 ThunkAction的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 redux-thunk `bindActionCreators`

如何使用 redux-thunk 和 try-catch 处理多个调度结果?

如何使用 redux-thunk 调度操作让 connected-react-router 重定向?

如何测试 redux-thunk 中间件异步功能?

如何在 Typescript 中使用 redux-thunk 使用 ThunkAction 正确键入 thunk?

使用 axios 和 redux-thunk 以 redux-form 发布请求