如何让TypeScript在GET端点添加DATA属性时触发编译错误?
TypeScript 强类型API端点:限制GET请求不能包含data属性
我正在前端客户端对API端点做强类型定义,现有代码如下:
type URI<T extends string> = { url: T } type QUERY<T> = { query: { [K in keyof T]: T[K] | T[K][] } } type DATA<T> = { data: Required<{ [K in keyof T]: T[K] }> } type PATCHDATA<T> = { data: { [K in keyof T]: T[K] } } type RESPONSE<T> = { response: { [K in keyof T]: T[K] } } type ENDPOINT = URI<any> & Partial<QUERY<any> & DATA<any> & PATCHDATA<any> & RESPONSE<any>>; type GET<T extends ENDPOINT> = T type POST<T extends ENDPOINT> = T; type GET_ACCOUNT = GET<DATA<{}> & URI<'account/:id'> & QUERY<{ userId: string }> & RESPONSE<{}>> type GET_ACCOUNTS = GET<URI<'accounts'> & QUERY<{ firstName: string, lastName: string }> & RESPONSE<{ id: string, firstname: string, lastName: string }>> type CREATE_ACCOUNT = POST<QUERY<{}> & URI<'accounts'> & DATA<{}> & RESPONSE<{}>> type GETS = GET_ACCOUNT | GET_ACCOUNTS type POSTS = CREATE_ACCOUNT type ACCOUNT_ENDPOINTS = GETS | POSTS
需求是:当尝试给GET端点添加DATA(比如GET_ACCOUNT里的DATA<{}>)时,编译器要抛出类似“GET请求不能拥有data属性”的错误。我试过下面两种写法,但都没效果:
type GET<T extends Omit<ENDPOINT, 'data'> = T type GET<T extends Pick<ENDPOINT, Exclude<keyof ENDPOINT, 'data'>>> = T
请问该怎么实现?
问题原因
之前的写法无效是因为ENDPOINT里的data是Partial类型(可选属性),Omit或Pick只是移除了data的定义,但当你把DATA<{}>(带有明确data属性的类型)和其他类型交叉时,最终的T仍然会包含data,原约束无法识别这种情况。
正确实现方式
方式一:带错误提示的条件类型检查
通过条件类型判断T是否包含data属性,一旦包含就返回带错误信息的类型,让编译器触发明确报错:
// 定义错误提示类型 type ApiError<T extends string> = { __errorMessage: T }; type URI<T extends string> = { url: T } type QUERY<T> = { query: { [K in keyof T]: T[K] | T[K][] } } type DATA<T> = { data: Required<{ [K in keyof T]: T[K] }> } type PATCHDATA<T> = { data: { [K in keyof T]: T[K] } } type RESPONSE<T> = { response: { [K in keyof T]: T[K] } } type ENDPOINT = URI<any> & Partial<QUERY<any> & DATA<any> & PATCHDATA<any> & RESPONSE<any>>; // 检查T是否包含data属性,包含则返回错误类型 type GET<T extends ENDPOINT> = T extends { data: any } ? ApiError<"GET请求不能拥有data属性"> : T; type POST<T extends ENDPOINT> = T; // 此处会触发错误:类型为带有__errorMessage的错误类型 type GET_ACCOUNT = GET<DATA<{}> & URI<'account/:id'> & QUERY<{ userId: string }> & RESPONSE<{}>> // 正常通过检查 type GET_ACCOUNTS = GET<URI<'accounts'> & QUERY<{ firstName: string, lastName: string }> & RESPONSE<{ id: string, firstname: string, lastName: string }>> type CREATE_ACCOUNT = POST<QUERY<{}> & URI<'accounts'> & DATA<{}> & RESPONSE<{}>> type GETS = GET_ACCOUNT | GET_ACCOUNTS type POSTS = CREATE_ACCOUNT type ACCOUNT_ENDPOINTS = GETS | POSTS
方式二:严格类型约束
直接通过类型约束禁止T包含data属性,触发类型不匹配错误:
type URI<T extends string> = { url: T } type QUERY<T> = { query: { [K in keyof T]: T[K] | T[K][] } } type DATA<T> = { data: Required<{ [K in keyof T]: T[K] }> } type PATCHDATA<T> = { data: { [K in keyof T]: T[K] } } type RESPONSE<T> = { response: { [K in keyof T]: T[K] } } type ENDPOINT = URI<any> & Partial<QUERY<any> & DATA<any> & PATCHDATA<any> & RESPONSE<any>>; // 约束T不能包含data属性(data只能是undefined或不存在) type GET<T extends ENDPOINT & { data?: never }> = T; type POST<T extends ENDPOINT> = T; // 此处直接触发类型不匹配错误 type GET_ACCOUNT = GET<DATA<{}> & URI<'account/:id'> & QUERY<{ userId: string }> & RESPONSE<{}>> type GET_ACCOUNTS = GET<URI<'accounts'> & QUERY<{ firstName: string, lastName: string }> & RESPONSE<{ id: string, firstname: string, lastName: string }>> type CREATE_ACCOUNT = POST<QUERY<{}> & URI<'accounts'> & DATA<{}> & RESPONSE<{}>> type GETS = GET_ACCOUNT | GET_ACCOUNTS type POSTS = CREATE_ACCOUNT type ACCOUNT_ENDPOINTS = GETS | POSTS
效果说明
- 给
GET传入带有DATA的类型时,编译器会立即报错,明确提示问题。 - 无
data属性的正常GET请求可以顺利通过类型检查。 - POST请求不受任何限制,依然可以正常使用
DATA属性。
内容的提问来源于stack exchange,提问作者shoop
相关产品推荐
相关产品推荐

