如何使用 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<IAppState, any, IAppActions>;
时神奇来了
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 重定向?