带有 redux-thunk 的类型安全的 useDispatch
type-safe useDispatch with redux-thunk
我正在使用 redux-thunk
来使用异步操作创建器。结果也返回给相应的调用者。
function fetchUserName(userId: number): Promise<string> {
return Promise.resolve(`User ${userId}`)
}
function requestUserName(userId: number) {
return (dispatch: Dispatch) => {
return fetchUserName(userId).then(name => {
dispatch({
type: 'SET_USERNAME',
payload: name,
})
})
}
}
这样,存储就更新了,同时允许组件直接处理响应。
function User() {
const dispatch = useDispatch()
useEffect(() => {
dispatch(requestUserName(1))
.then(name => {
console.log(`user name is ${name}`)
})
.catch(reason => {
alert('failed fetching user name')
})
}, [])
}
这是按预期工作的,但由于类型无效,它不会被 TypeScript 编译。
useDispatch
返回的 dispatch
未被识别为 returns Promise 的函数,因此 TypeScript 认为 Property 'then' does not exist on type '(dispatch: Dispatch<AnyAction>) => Promise<void>'.
.
- 即使可以识别,Promise 也应该正确输入
这种情况如何解决?
我可以围绕 useDispatch
创建一个包装器或重新定义 dispatch
的类型,但我不知道在这种特定情况下该类型应该是什么样子。
非常感谢您的任何建议。
useDispatch
returns Dispatch
类型 used by Redux, so you can only dispatch standard actions with it. To also dispatch thunk actions, declare its type as ThunkDispatch
(来自 redux-thunk
)。
ThunkDispatch
接收存储状态的类型参数,extra thunk args and your action type. It allows to dispatch a ThunkAction
,这基本上是 requestUserName
.
的内部函数
例如,您可以这样输入:
import { ThunkDispatch } from "redux-thunk";
import { AnyAction } from "redux";
type State = { a: string }; // your state type
type AppDispatch = ThunkDispatch<State, any, AnyAction>;
// or restrict to specific actions instead of AnyAction
function User() {
const dispatch: AppDispatch = useDispatch();
useEffect(() => {
dispatch(requestUserName(1))
.then(...) // works now
}, []);
...
}
AppDispatch
也可以 inferred 从商店 typeof store.dispatch
:
import thunk, { ThunkDispatch, ThunkMiddleware } from "redux-thunk";
const mw: ThunkMiddleware<State, AnyAction> = thunk;
const dummyReducer = (s: State | undefined, a: AnyAction) => ({} as State);
const store = createStore(dummyReducer, applyMiddleware(mw));
type AppDispatch = typeof store.dispatch // <-- get the type from store
另请参阅 redux 关于使用带钩子的打字稿的文档:https://redux.js.org/usage/usage-with-typescript#define-typed-hooks
我的常用设置包括类型安全 AppDispatch
和类型挂钩;
import { createStore, applyMiddleware, PreloadedState, combineReducers } from 'redux';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import thunk, { ThunkDispatch } from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
const middlewares = [thunk];
const enhancer = composeWithDevTools({ /* optional actionsBlacklist, etc */ });
const reducers = combineReducers({ /* ... */ })
export type RootState = ReturnType<typeof reducers>;
export const initStore = (initState?: PreloadedState<RootState>) =>
createStore(reducers, initState, enhancer(applyMiddleware(...middlewares)));
export const store = initStore();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
type AppAction = ReturnType<typeof store.dispatch>;
export type AppDispatch = ThunkDispatch<RootState, any, AppAction>;
export const useAppDispatch = () => useDispatch<AppDispatch>();
请注意,Redux Toolkit 会略有不同,因为您 configureStore
而不是 combineReducers
我正在使用 redux-thunk
来使用异步操作创建器。结果也返回给相应的调用者。
function fetchUserName(userId: number): Promise<string> {
return Promise.resolve(`User ${userId}`)
}
function requestUserName(userId: number) {
return (dispatch: Dispatch) => {
return fetchUserName(userId).then(name => {
dispatch({
type: 'SET_USERNAME',
payload: name,
})
})
}
}
这样,存储就更新了,同时允许组件直接处理响应。
function User() {
const dispatch = useDispatch()
useEffect(() => {
dispatch(requestUserName(1))
.then(name => {
console.log(`user name is ${name}`)
})
.catch(reason => {
alert('failed fetching user name')
})
}, [])
}
这是按预期工作的,但由于类型无效,它不会被 TypeScript 编译。
useDispatch
返回的dispatch
未被识别为 returns Promise 的函数,因此 TypeScript 认为Property 'then' does not exist on type '(dispatch: Dispatch<AnyAction>) => Promise<void>'.
.- 即使可以识别,Promise 也应该正确输入
这种情况如何解决?
我可以围绕 useDispatch
创建一个包装器或重新定义 dispatch
的类型,但我不知道在这种特定情况下该类型应该是什么样子。
非常感谢您的任何建议。
useDispatch
returns Dispatch
类型 used by Redux, so you can only dispatch standard actions with it. To also dispatch thunk actions, declare its type as ThunkDispatch
(来自 redux-thunk
)。
ThunkDispatch
接收存储状态的类型参数,extra thunk args and your action type. It allows to dispatch a ThunkAction
,这基本上是 requestUserName
.
例如,您可以这样输入:
import { ThunkDispatch } from "redux-thunk";
import { AnyAction } from "redux";
type State = { a: string }; // your state type
type AppDispatch = ThunkDispatch<State, any, AnyAction>;
// or restrict to specific actions instead of AnyAction
function User() {
const dispatch: AppDispatch = useDispatch();
useEffect(() => {
dispatch(requestUserName(1))
.then(...) // works now
}, []);
...
}
AppDispatch
也可以 inferred 从商店 typeof store.dispatch
:
import thunk, { ThunkDispatch, ThunkMiddleware } from "redux-thunk";
const mw: ThunkMiddleware<State, AnyAction> = thunk;
const dummyReducer = (s: State | undefined, a: AnyAction) => ({} as State);
const store = createStore(dummyReducer, applyMiddleware(mw));
type AppDispatch = typeof store.dispatch // <-- get the type from store
另请参阅 redux 关于使用带钩子的打字稿的文档:https://redux.js.org/usage/usage-with-typescript#define-typed-hooks
我的常用设置包括类型安全 AppDispatch
和类型挂钩;
import { createStore, applyMiddleware, PreloadedState, combineReducers } from 'redux';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import thunk, { ThunkDispatch } from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
const middlewares = [thunk];
const enhancer = composeWithDevTools({ /* optional actionsBlacklist, etc */ });
const reducers = combineReducers({ /* ... */ })
export type RootState = ReturnType<typeof reducers>;
export const initStore = (initState?: PreloadedState<RootState>) =>
createStore(reducers, initState, enhancer(applyMiddleware(...middlewares)));
export const store = initStore();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
type AppAction = ReturnType<typeof store.dispatch>;
export type AppDispatch = ThunkDispatch<RootState, any, AppAction>;
export const useAppDispatch = () => useDispatch<AppDispatch>();
请注意,Redux Toolkit 会略有不同,因为您 configureStore
而不是 combineReducers