函数的类型推断作为Typescript中的参数

时间:2019-10-18 08:58:24

标签: reactjs typescript function react-hooks type-inference

我的情况如下图所示-我的函数接受对象形式的选项,其中一个参数为transform函数。此函数接受response参数,该参数的类型已为整个函数正确计算(第一张和第二张图片),但是我的打字稿编译器将response自变量隐式视为any类型(第三张图片)。我无法弄清楚为什么它不能正确假定正确的response类型,在这种情况下应该为ApiResponse<NewsListApiResponseData>

First picture Second picture Third picture

Error:(27, 20) TS7006: Parameter 'response' implicitly has an 'any' type.

在这种情况下,这是我的useAxios重载钩子定义:

export function useAxios<ResponseData, TransformedData = false | null | ResponseData>(
    endpoint: string,
    options: {
        autoStart?: boolean;
        transform: (response: ApiResponse<ResponseData>) => TransformedData;
        onResolve?: (data: TransformedData) => void;
        onReject?: (error: Error) => void;
    } & AxiosHookRequestConfig
): {
    loading: boolean;
    canceled: boolean;
    error?: Error;
    response?: AxiosResponse<ApiResponse<ResponseData>>;
    data?: TransformedData;
    request: (config?: AxiosHookRequestConfig) => Promise<TransformedData>;
    cancel: (reason?: string) => void;
};

编辑:添加了AxiosHookRequestConfig定义。

export interface AxiosHookRequestConfig extends Omit<AxiosRequestConfig, 'url' | 'cancelToken'> {
    page?: number;
    lang?: string;
    cache?: boolean | string;
}

export interface AxiosRequestConfig {
  url?: string;
  method?: Method;
  baseURL?: string;
  transformRequest?: AxiosTransformer | AxiosTransformer[];
  transformResponse?: AxiosTransformer | AxiosTransformer[];
  headers?: any;
  params?: any;
  paramsSerializer?: (params: any) => string;
  data?: any;
  timeout?: number;
  withCredentials?: boolean;
  adapter?: AxiosAdapter;
  auth?: AxiosBasicCredentials;
  responseType?: ResponseType;
  xsrfCookieName?: string;
  xsrfHeaderName?: string;
  onUploadProgress?: (progressEvent: any) => void;
  onDownloadProgress?: (progressEvent: any) => void;
  maxContentLength?: number;
  validateStatus?: (status: number) => boolean;
  maxRedirects?: number;
  socketPath?: string | null;
  httpAgent?: any;
  httpsAgent?: any;
  proxy?: AxiosProxyConfig | false;
  cancelToken?: CancelToken;
}

Edit2:Example

1 个答案:

答案 0 :(得分:1)

让我们看看我认为这个问题的实质:

declare function foo<T>(x: { prop: T }): void;
declare function foo<T>(x: { prop: T, func: (x: T) => void }): void;
foo({ prop: "hey", func: x => x.length }); // error!
//   ┌─────────────────> ~
// Parameter 'x' implicitly has an 'any' type.

情况与optimizer中的情况相同,标记为“按预期工作”。问题在于该调用与第一个重载签名匹配,因此编译器无法弄清楚如何推断x的类型。

它如何匹配两个签名?答案是TypeScript中的对象类型不是this reported issue。如果您有一个类似interface Foo {a: string}的接口和一个interface Bar extends Foo {b: string}的接口,这是子类型化的事实,即Bar的每个实例也是一个Foo。这意味着,一般来说,像{a: string}这样的类型与其中具有附加属性的任何对象都兼容,只要它具有名为a的字符串值的属性即可。通常,编译器会尝试通过exact来帮助人们不要犯错误,但是由于func属于它正在检查的一种类型,因此这些检查似乎没有在这里进行。不知道为什么,也许这是设计限制或错误。

无论如何,编译器将func视为某种函数类型,但是由于它与第一个重载匹配,因此x的上下文类型输入不起作用,并且您会收到“隐含任何”错误。


这里有几种方法可以进行。一种是改变重载的顺序,以便第一个重载更具限制性。顶部的抓斗超载标志最终阻止了超载解决方案越过它。总的来说,这是一个好规则:顶部的更具体的重载,底部的更一般的重载:

declare function foo<T>(x: { prop: T, func: (x: T) => void }): void;
declare function foo<T>(x: { prop: T }): void;
foo({ prop: "hey", func: x => x.length }); // okay

选择第一个重载,并且知道func的类型,并将x推断为string


另一种处理方式是进行更一般的重载并进行更改,以使其实际上禁止有问题的呼叫,方法是将func设置为它无法拥有的属性,例如:

declare function foo<T>(x: { prop: T, func?: never }): void;
declare function foo<T>(x: { prop: T, func: (x: T) => void }): void;
foo({ prop: "hey", func: x => x.length }); // okay

之所以现在有效,是因为在第一次重载中,func是一个可选属性,其值的类型为never。除了undefined以外,别无他法,而x => x.length之类的函数肯定不满足。因此,该调用将跳过第一个重载,选择第二个重载,并为string推断x


最后,在所讨论的两个重载非常相似的情况下,除了可能存在的属性外,我倾向于将它们折叠为单个签名,而完全忘记了重载。这可能与您的用例不符,但请记住以下几点:

declare function foo<T>(x: { prop: T, func?: (x: T) => void }): void;
foo({ prop: "hey", func: x => x.length }); // okay

现在只有一个呼叫签名,并且func可以存在或不存在。


其中之一有望为您工作。 warning about excess properties,物有所值。

好的,祝你好运!

I've tested the second one on your example