问题
I have the following async action definition:
import {Dispatch} from 'react';
import axios, {AxiosResponse, AxiosError} from 'axios';
function asyncAction() {
return (dispatch: Dispatch<any>): Promise<number> => {
return axios.get('http://www.example.com')
.then( (res: AxiosResponse<any>) => {
return 1;
})
.catch( (err: AxiosError<any>) => {
return 2;
});
}
}
The above typechecks fine.
I also understand that when you call dispatch
and pass it an asynchronous action, like so:
dispatch(asynAction())
… then the return type if that of the inner function, so I would expect the type of the above value to be Promise<number>
. Yet the following does not typecheck:
function foo (dispatch: Dispatch<any>) {
const n: Promise<number> = dispatch(asyncAction()); // line A
}
Specifically, I get the following error on line A
:
TS2322: Type 'void' is not assignable to type 'Promise<number>'
So, in order to satisfy TS, I have to do something like the following which feels wrong:
const n: Promise<number> = dispatch(asyncAction()) as unknown as Promise<number>;
What am I missing?
update
My `package.json` has:"@types/react-redux": "^7.1.9",
"react-redux": "^7.2.0",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"redux-thunk": "^2.3.0"
When I do the following:
import {ThunkDispatch as Dispatch} from 'redux-thunk';
… and use the imported ThunkDispatch
type as ThunkDispatch<any, any, any>
(wherever I have Dispatch<any>
in the above code), like so:
import axios, {AxiosResponse
, AxiosError} from 'axios';
import {ThunkDispatch as Dispatch} from 'redux-thunk';
export function asyncAction() {
return (dispatch: Dispatch<any, any, any>): Promise<number> => {
return axios.get('http://www.example.com')
.then( (res: AxiosResponse<any>) => {
return 1;
})
.catch( (err: AxiosError<any>) => {
return 2;
});
}
}
export function foo (dispatch: Dispatch<any, any, any>) {
const n: Promise<number> = dispatch(asyncAction());
console.log(n);
}
… I get a different error:
TS2739: Type '(dispatch: ThunkDispatch<any, any, any>) => Promise<number>' is missing the following properties from type 'Promise<number>': then, catch, [Symbol.toStringTag]
回答1:
Different packages have different definitions for the Dispatch
type. You are importing the one from 'react', which does not suit your needs.
Redux declares that a dispatch returns the action:
export interface Dispatch<A extends Action = AnyAction> {
<T extends A>(action: T): T
}
React, which defines Dispatch
for the purposes of it's own useReducer
hook (redux-lite), says that it returns nothing:
type Dispatch<A> = (value: A) => void;
Thunk has a much more complicated definition which allows it to return any arbitrary return type base on a generic:
export interface Dispatch<A extends Action = AnyAction> {
<TReturnType = any, TState = any, TExtraThunkArg = any>(
thunkAction: ThunkAction<TReturnType, TState, TExtraThunkArg, A>,
): TReturnType;
}
Your first example worked because you never actually called the dispatch
function and you return type matched the return type from the axios
call. You're having the same problem in your second example.
asyncAction()
doesn't dispatch the action. Promise<number>
is the return type of the action creator, not of the dispatch. The error about missing properties is basically telling you that the dispatch doesn't return a Promise
.
You've written asyncAction
in a very confused and confusing way, but basically it is a function which returns a function which takes a dispatch
and returns Promise<number>
. So based on that, the way you get Promise<number>
in foo
is not by calling dispatch
with your function, but by passing dispatch
as the argument to the function created by calling ayncAction()
.
export function foo (dispatch: Dispatch<any, any, any>) {
const n: Promise<number> = asyncAction()(dispatch);
console.log(n);
}
This of course makes no sense, since you still never dispatch anything anywhere.
Typescript Playground Link
Edit:
Hopefully this helps with what you're tying to do. Hopefully getting n
from the return value of the dispatch
was just for testing because you can't and shouldn't do that.
Let's define the action that we will ultimately dispatch, along with a creator for it.
import { Action } from "redux";
// normal action type
interface NumberAction extends Action {
payload: number;
}
// normal action creator
function sendNumber(number: number): NumberAction {
return { type: 'SEND_NUMBER', payload: number };
}
We make a ThunkAction
that dispatches this NumberAction
. It is a function that takes dispatch
as an argument and returns a Promise
for the action that it ultimately dispatches.
const myThunkAction1: ThunkAction<Promise<NumberAction>, any, any, NumberAction> = (dispatch): Promise<NumberAction> => {
return axios.get('http://www.example.com')
.then((res: AxiosResponse<any>) => {
return 1;
})
.catch((err: AxiosError<any>) => {
return 2;
})
.then(number => dispatch(sendNumber(number)));
}
We can dispatch
this, but it returns the ThunkAction
itself. In other words, calling dispatch
returns a function which takes dispatch
. So you cannot get anything meaningful from the return value of dispatching a thunk.
export function foo(dispatch: Dispatch<any, any, any>) {
// still doesn't return Promise<number>, returns the ThunkAction
const returned = dispatch(myThunkAction2);
// can only get to Promise<number> by calling the returned thunk, which would re-dispatch the action
const n: Promise<number> = returned(dispatch, () => {}, {});
console.log(n);
}
Second Playground Link
来源:https://stackoverflow.com/questions/64525082/typescript-type-of-an-async-dispatch-value