我正在尝试实现一个简约的类型安全函数,该函数可以针对类型化服务器 API 执行 HTTP 请求,但我正在与类型作斗争。具体来说,如何使第三个参数(HTTP 请求负载)对于
GET
和 DELETE
请求是可选的,同时对其进行验证并且对于 POST
和 PUT
是非可选的。
简化示例(TypeScript Playground):
export type Method = 'GET' | 'DELETE' | 'POST' | 'PUT'
// attempt 1:
const httpRequest1 = <M, D>(method: M, url: string, data?: D) =>
'not implemented yet' as any
// attempt 2:
type HttpRequest <M extends Method, D, R=unknown> = M extends 'GET' | 'DELETE'
? (method: M, url: string) => Promise<R>
: (method: M, url: string, data: D) => Promise<R>
const httpRequest2: HttpRequest = (method, url, data) =>
'not implemented yet' as any
// expected usage:
interface Data {
title: string;
}
httpRequest<'POST', Data>('POST', '/', { title: 'test' }) // should typecheck
httpRequest<'POST', Data>('POST', '/', { titleoops: 'test' }) // should NOT typecheck
httpRequest<'POST', Data>('POST', '/') // should NOT typecheck
httpRequest<'GET', undefined>('GET', '/') // should typecheck
我从来没有按照预期同时对所有四个条件进行类型检查。至少有一个总是坏掉的。
附注如果您想知道,playground 链接到完整的用例,这有点复杂。
您可以使用函数重载。
您创建一个可以在运行时处理任何情况的函数,然后为其提供许多充当不同重载的类型声明。
export type Method = 'GET' | 'DELETE' | 'POST' | 'PUT'
function httpRequest(method: 'GET' | 'DELETE', url: string, data?: Data): any;
function httpRequest(method: 'POST' | 'PUT', url: string, data: Data): any;
function httpRequest(method: Method, url: string, data?: Data) {
return 'not implemented yet' as any
}
// expected usage:
interface Data {
title: string;
}
httpRequest('POST', '/test', { title: 'test' }) // should typecheck
httpRequest('POST', '/test', { titleoops: 'test' }) // should not typecheck
httpRequest('POST', '/test') // should not typecheck
httpRequest('GET', '/test') // should typecheck
在调用站点,TS 将确保您的参数与其中一个重载相匹配。