[英]Default callback return type conflicts with generic type which should be inferred as that return type
我正在研究一个 React 钩子,我遇到了一个问题,我想从传递给钩子的 function 的返回类型中推断出部分返回类型,但我并没有完全让它工作:
const useFetch = <ResponseType>(
fetchInputFactory: () => RequestInfo | URL | [RequestInfo | URL, RequestInit],
deps: DependencyList | undefined,
transformResponse: (response: Response) => Promise<ResponseType> = async (response) => response
):
| { loading: true }
| { loading: false; success: true; response: ResponseType }
| { loading: false; success: false; error: Error }
我得到的错误是:
(parameter) response: Response
Type 'Promise<Response>' is not assignable to type 'Promise<ResponseType>'.
Type 'Response' is not assignable to type 'ResponseType'.
'ResponseType' could be instantiated with an arbitrary type which could be unrelated to 'Response'.ts(2322)
index.ts(6, 22): The expected type comes from the return type of this signature.
不幸的是,泛型类型总是可以由消费者代码显式指定。
因此,即使未提供第三个参数transformResponse
,它默认为异步标识 function,我们预计 TypeScript 会推断ResponseType
只是Response
,此推断可能会被消费代码覆盖:
const f = useFetch<number>(someFetchInputFactoryFn, []);
// ^? { loading: true } | { loading: false; success: true; response: number } | { loading: false; success: false; error: Error }
// TS expects `response` to be a number, but it should be a `Response`...
限制泛型类型是不够的,因为非常糟糕的消费代码总是可以显式指定扩展Response
的类型,但如果它不提供相应的transformResponse
参数,则挂钩实现无法匹配该任意类型。
因此,我们希望防止在未提供transformResponse
时显式指定泛型类型的可能性,以便在这种情况下响应类型将始终为Response
。 但是如果提供了它,那么我们需要泛型类型(并且如果消费代码仍然试图显式传递一个不匹配的类型,TS 将对其transformResponse
值大喊大叫,而不是在钩子默认值上)。
对于这种情况,我们可以使用function 重载,这样在一种情况下没有generics 可以处理,而在另一种情况下,挂钩返回类型与传递的transformResponse
回调返回类型相关联:
// A type to simplify the return expression
type HookReturn<ResponseType> =
| { loading: true }
| { loading: false; success: true; response: ResponseType }
| { loading: false; success: false; error: Error }
// Note: omitting the first 2 arguments, since they do not play a role in the issue
function useFetch2(): HookReturn<Response>;
function useFetch2<ResponseType>(transformResponse: (response: Response) => Promise<ResponseType>): HookReturn<ResponseType>;
function useFetch2(transformResponse = (async (response: Response) => response)) {
return { // Dummy implementation
loading: true
}
}
让我们检查一下它的用法:
// Form with no transformResponse, defaults to async identity, and return type to Response
const a = useFetch2() // Okay
// ^? HookReturn<Response>
// Form with no transformResponse, but trying to specify a generic type
const b = useFetch2<number>() // Error: Expected 1 arguments, but got 0.
// ~~~~~~~~~~~~~~~~~~~
// ^? HookReturn<Response>
// Form with provided transformResponse, return type is inferred from the callback return
const c = useFetch2(async () => true) // Okay
// ^? HookReturn<boolean>
// Form with provided transformResponse, explicitly specifying a mismatching generic type
const d = useFetch2<number>(async () => true) // Error: Type 'boolean' is not assignable to type 'number'.
// ~~~~
// ^? HookReturn<number>
您为transformResponse
提供的默认 function 返回Promise<Response>
而不是Promise<ResponseType>
您可能可以强制转换响应并将默认类型添加到 ReponseType = Response
const useFetch = <ResponseType = Response>(
fetchInputFactory: () => RequestInfo | URL | [RequestInfo | URL, RequestInit],
deps: DependencyList | undefined,
transformResponse: (response: Response) => Promise<ResponseType> = async (response) => response as ResponseType
):
| { loading: true }
| { loading: false; success: true; response: ResponseType }
| { loading: false; success: false; error: Error }
我猜这更好地描述了默认行为
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.