如何使用 redux-thunk 和 TypeScript 调度 ThunkAction
How to dispatch ThunkAction with redux-thunk and TypeScript
我在使用 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'.
我尝试了多种不同的操作类型思考,例如自定义界面、操作等,但没有任何效果。
默认 dispatch
类型不知道 thunk,因为“base redux”类型不是很强大。所以你必须手动将它投射到 ThunkDispatch:
(store.dispatch as ThunkDispatch<State, unknown, AnyAction>)(increment())
作为 PSA:您在此处编写的 redux 类型(具有手写操作、操作类型、switch-case 语句和 reducer 中的不可变逻辑的 vanilla redux)不再是“官方推荐的方法”编写redux。
请查看 redux toolkit and best follow the official, up-to-date redux tutorials,因为您很可能关注的是一个非常过时的版本。
Redux 工具包也是 很多 通常更容易使用,特别是与 TypeScript 一起使用(如果你使用它,store.dispatch
将具有正确的类型;))
给那些在使用 thunk 和 hooks 时遇到 dispatch 函数问题的人提个建议。
这是我管理身份验证状态、从 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 分派异步操作的组件示例
尽管它是从 react-redux
导入的,但请不要将其键入为 IAppDispatch
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 };
我最近在尝试将我的应用程序从 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());
目前推荐的使用 typescript 设置 React-Redux
的方法是使用 Redux Toolkit
,可以找到指南 here。
我在使用 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'.
我尝试了多种不同的操作类型思考,例如自定义界面、操作等,但没有任何效果。
默认 dispatch
类型不知道 thunk,因为“base redux”类型不是很强大。所以你必须手动将它投射到 ThunkDispatch:
(store.dispatch as ThunkDispatch<State, unknown, AnyAction>)(increment())
作为 PSA:您在此处编写的 redux 类型(具有手写操作、操作类型、switch-case 语句和 reducer 中的不可变逻辑的 vanilla redux)不再是“官方推荐的方法”编写redux。 请查看 redux toolkit and best follow the official, up-to-date redux tutorials,因为您很可能关注的是一个非常过时的版本。
Redux 工具包也是 很多 通常更容易使用,特别是与 TypeScript 一起使用(如果你使用它,store.dispatch
将具有正确的类型;))
给那些在使用 thunk 和 hooks 时遇到 dispatch 函数问题的人提个建议。
这是我管理身份验证状态、从 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 分派异步操作的组件示例
尽管它是从 react-redux
导入的,但请不要将其键入为 IAppDispatch 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 };
我最近在尝试将我的应用程序从 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());
目前推荐的使用 typescript 设置 React-Redux
的方法是使用 Redux Toolkit
,可以找到指南 here。