函数的类型推断作为 Typescript 中的参数
Type inference of function as argument in Typescript
我的情况如下图所示 - 我的函数接受对象形式的选项,其中一个参数是 transform
函数。此函数接受 response
参数,该参数为整个函数(第一张和第二张图片)正确计算了该类型,但我的打字稿编译器将 response
参数隐式视为 any
类型(第三张图片)。我不明白为什么它不能正确假设正确的 response
类型,在那种情况下应该是 ApiResponse<NewsListApiResponseData>
?
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;
}
编辑 2:Example
让我们看看我认为这个问题的本质是什么:
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.
情况同this reported issue,标记为"working as intended"。问题是调用与第一个重载签名匹配,因此编译器无法弄清楚如何推断 x
的类型。
它如何匹配两个签名?答案是 TypeScript 中的对象类型不是 exact. If you have an interface like interface Foo {a: string}
, and an interface interface Bar extends Foo {b: string}
, it is a fact of subtyping that every instance of Bar
is also a Foo
. Meaning that, in general, a type like {a: string}
is compatible with any object with extra properties in it, as long as it has a string-valued property named a
. Often the compiler tries to help people not make mistakes by warning about excess properties,但这些检查似乎不会在这里发生,因为 func
是它正在检查的类型之一。不知道为什么,也许这是设计限制或错误。
无论如何,编译器将 func
视为某种函数类型,但由于它匹配第一个重载,因此 x
的上下文类型不起作用,你会得到 "implicit any" 错误。
这里有几种方法可以继续。一种是改变重载的顺序,使第一个重载更具限制性。顶部的 grab-bag 过载签名最终阻止了过载解析通过它。总的来说这是一个很好的规则:更具体的重载在顶部,更一般的重载在底部:
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
这样的函数肯定不会。所以调用跳过第一个重载,选择第二个,并为 x
.
推断 string
最后,如果所讨论的两个重载除了可能存在 属性 之外非常相似,我倾向于将它们折叠成一个签名并完全忘记重载。这可能与您的用例不匹配,但请记住:
declare function foo<T>(x: { prop: T, func?: (x: T) => void }): void;
foo({ prop: "hey", func: x => x.length }); // okay
现在只有一个调用签名,func
可以存在也可以不存在。
希望其中之一对您有用。 I've tested the second one on your example,物有所值。
好的,祝你好运!
我的情况如下图所示 - 我的函数接受对象形式的选项,其中一个参数是 transform
函数。此函数接受 response
参数,该参数为整个函数(第一张和第二张图片)正确计算了该类型,但我的打字稿编译器将 response
参数隐式视为 any
类型(第三张图片)。我不明白为什么它不能正确假设正确的 response
类型,在那种情况下应该是 ApiResponse<NewsListApiResponseData>
?
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;
}
编辑 2:Example
让我们看看我认为这个问题的本质是什么:
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.
情况同this reported issue,标记为"working as intended"。问题是调用与第一个重载签名匹配,因此编译器无法弄清楚如何推断 x
的类型。
它如何匹配两个签名?答案是 TypeScript 中的对象类型不是 exact. If you have an interface like interface Foo {a: string}
, and an interface interface Bar extends Foo {b: string}
, it is a fact of subtyping that every instance of Bar
is also a Foo
. Meaning that, in general, a type like {a: string}
is compatible with any object with extra properties in it, as long as it has a string-valued property named a
. Often the compiler tries to help people not make mistakes by warning about excess properties,但这些检查似乎不会在这里发生,因为 func
是它正在检查的类型之一。不知道为什么,也许这是设计限制或错误。
无论如何,编译器将 func
视为某种函数类型,但由于它匹配第一个重载,因此 x
的上下文类型不起作用,你会得到 "implicit any" 错误。
这里有几种方法可以继续。一种是改变重载的顺序,使第一个重载更具限制性。顶部的 grab-bag 过载签名最终阻止了过载解析通过它。总的来说这是一个很好的规则:更具体的重载在顶部,更一般的重载在底部:
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
这样的函数肯定不会。所以调用跳过第一个重载,选择第二个,并为 x
.
string
最后,如果所讨论的两个重载除了可能存在 属性 之外非常相似,我倾向于将它们折叠成一个签名并完全忘记重载。这可能与您的用例不匹配,但请记住:
declare function foo<T>(x: { prop: T, func?: (x: T) => void }): void;
foo({ prop: "hey", func: x => x.length }); // okay
现在只有一个调用签名,func
可以存在也可以不存在。
希望其中之一对您有用。 I've tested the second one on your example,物有所值。
好的,祝你好运!