You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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']>;
}

方案说明

  1. 泛型约束修正:M extends keyof APIDef[A]确保method参数只能传入当前API路径下已定义的方法(如/api/api1仅能传GET或POST),避免了冗余定义未使用的HTTP方法。
  2. 类型断言调整:将method断言为string是因为fetch的method参数接受字符串类型,而M是字面量类型;同时直接将json()的返回值断言为目标响应类型,替代原代码中冗余的ReturnType引用。
  3. 类型推导验证:调用callAPI('/api/api1', 'GET', { api1getparam1: 'test', api1getparam2: 123 })时,TypeScript会自动推导请求参数的类型,并提示必填字段;响应类型也会自动匹配APIDef['/api/api1']['GET']['response']。

内容的提问来源于stack exchange,提问作者wingerse

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 08:55:21