TypeScript type of an async dispatch value

妖精的绣舞 提交于 2021-01-29 07:20:28

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!