TypeScript二次泛型索引类型推导失败,求无冗余方法定义方案
解决TypeScript API请求函数的泛型类型推导冗余问题
问题背景
需要实现一个接收API路径字符串和请求方法的函数,自动推导请求参数与响应数据的类型。最初尝试的代码中,TypeScript无法正确识别APIDef[A][M]的类型;临时方案通过固定HTTP方法联合类型实现,但要求所有API定义必须包含全部HTTP方法(即使未使用),存在冗余。
最初尝试代码
interface APIDef { '/api/api1': { GET: { request: { api1getparam1: string, api1getparam2: number, }, response: { api1getres1: string[], api1getres2: number[], } }, POST: { request: { api1postparam1: string, api1postparam2: number, }, response: { api1postres1: string[], api1postres2: number[], } }, }, '/api/api2': { GET: { request: { api2getparam1: boolean, api2getparam2: number, }, response: { api2getres1: string[], api2getres2: number[], } }, POST: { request: { api2postparam1: boolean }, response: { api2postres1: 'success', } }, }, } async function callAPI<A extends keyof APIDef, M extends APIDef[A]>(api: A, method: M, request: APIDef[A][M]['request']): Promise<APIDef[A][M]['response']> { return await (await fetch(api, { method: method, body: JSON.stringify(request) })).json() as ReturnType<typeof callAPI<A, M>>; }
临时冗余方案(需避免)
interface APIDef { '/api/api1': { GET: { request: { api1getparam1: string, api1getparam2: number, }, response: { api1getres1: string[], api1getres2: number[], } }, POST: { request: { api1postparam1: string, api1postparam2: number, }, response: { api1postres1: string[], api1postres2: number[], } }, PUT: never, PATCH: never, DELETE: never, HEAD: never, }, '/api/api2': { GET: { request: { api2getparam1: boolean, api2getparam2: number, }, response: { api2getres1: string[], api2getres2: number[], } }, POST: { request: { api2postparam1: boolean }, response: { api2postres1: 'success', } }, PUT: never, PATCH: never, DELETE: never, HEAD: never, }, } type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD'; async function callAPI<A extends keyof APIDef, M extends Method>(api: A, method: M, request: APIDef[A][M]['request']): Promise<APIDef[A][M]['response']> { return await (await fetch(api, { method: method, body: JSON.stringify(request) })).json() as ReturnType<typeof callAPI<A, M>>; }
优化方案
核心是修正泛型约束:将M的约束从APIDef[A]改为keyof APIDef[A],即让M仅代表当前API路径下已定义的请求方法,无需冗余填充未使用的HTTP方法。同时调整类型断言的写法,确保类型推导正确。
优化后代码
interface APIDef { '/api/api1': { GET: { request: { api1getparam1: string, api1getparam2: number, }, response: { api1getres1: string[], api1getres2: number[], } }, POST: { request: { api1postparam1: string, api1postparam2: number, }, response: { api1postres1: string[], api1postres2: number[], } }, }, '/api/api2': { GET: { request: { api2getparam1: boolean, api2getparam2: number, }, response: { api2getres1: string[], api2getres2: number[], } }, POST: { request: { api2postparam1: boolean }, response: { api2postres1: 'success', } }, }, } async function callAPI< A extends keyof APIDef, M extends keyof APIDef[A] >( api: A, method: M, request: APIDef[A][M]['request'] ): Promise<APIDef[A][M]['response']> { const response = await fetch(api, { method: method as string, // 类型断言将方法名转为fetch接受的string类型 body: JSON.stringify(request) }); return response.json() as Promise<APIDef[A][M]['response']>; }
方案说明
- 泛型约束修正:
M extends keyof APIDef[A]确保method参数只能传入当前API路径下已定义的方法(如/api/api1仅能传GET或POST),避免了冗余定义未使用的HTTP方法。 - 类型断言调整:将
method断言为string是因为fetch的method参数接受字符串类型,而M是字面量类型;同时直接将json()的返回值断言为目标响应类型,替代原代码中冗余的ReturnType引用。 - 类型推导验证:调用
callAPI('/api/api1', 'GET', { api1getparam1: 'test', api1getparam2: 123 })时,TypeScript会自动推导请求参数的类型,并提示必填字段;响应类型也会自动匹配APIDef['/api/api1']['GET']['response']。
内容的提问来源于stack exchange,提问作者wingerse
相关产品推荐
相关产品推荐

