【问题标题】:How to dispatch ThunkAction with redux-thunk and TypeScript如何使用 redux-thunk 和 TypeScript 调度 ThunkAction
【发布时间】: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'.

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

【问题讨论】:

    标签: typescript redux redux-thunk


    【解决方案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找到指南。

      【讨论】:

        猜你喜欢
        • 2017-08-18
        • 2021-01-01
        • 2019-03-23
        • 2021-02-11
        • 2021-08-24
        • 2020-08-18
        • 2019-10-23
        • 2021-04-05
        • 2020-03-17
        相关资源
        最近更新 更多