基于条件类型提取可选参数的TypeScript API客户端优化方案
重构TypeScript API客户端的
buildUrl函数:类型安全与可读性优化 目标API端点
需要对接的API端点如下:
/** * https://myapi.com/tokens/eth/${address} * https://myapi.com/tokens/security/eth/${address} * https://myapi.com/rank/eth/wallets/7d?orderby=pnl_7d&direction=desc&tag=snipe_bot * https://myapi.com/smartmoney/eth/balances/${userAddress}?limit=100&orderby=last_active_timestamp&direction=desc&tx30d=true * https://myapi.com/wallet_token_activity?wallet=${userAddress}&token=${tokenAddress}&limit=50 */
现有实现代码
当前的TypeScript代码实现如下:
type EthereumAddress = `0x${string}` type ApiMethods = { getToken(): string getTokenSecurity(): string getRanking(args: { param: string }): string getBalances(args: { param: string }): string getActivity(args: { param: string }): string } type ApiPathsStruct = { [K in keyof ApiMethods]: { path: string hasAddress: boolean } } const apiPaths = { getToken: { path: '/tokens/eth', hasAddress: true }, getTokenSecurity: { path: '/tokens/security/eth', hasAddress: true }, getRanking: { path: '/rank/eth/wallets/7d', hasAddress: false }, getBalances: { path: '/smartmoney/eth/balances', hasAddress: true }, getActivity: { path: '/wallet_token_activity', hasAddress: false } } as const satisfies ApiPathsStruct type ApiPaths = typeof apiPaths type Path<K extends keyof ApiPaths> = keyof { [T in ApiPaths[K]as T extends { hasAddress: true } ? `${T["path"]}/${EthereumAddress}` : T["path"]]: 0 } type Payload<K extends keyof ApiMethods> = Parameters< ApiMethods[K] >[0] extends undefined ? never : Parameters<ApiMethods[K]>[0] function buildUrl<M extends keyof ApiMethods>( method: M, ...args: (ApiPaths[M] extends { hasAddress: true } ? (Payload<M> extends never ? [EthereumAddress] : [EthereumAddress, Payload<M>] ) : (Payload<M> extends never ? [undefined?] : [Payload<M>] ) ) ) { let API_BASE = 'https://myapi.com' let address: EthereumAddress let payload: Payload<M> if (apiPaths[method].hasAddress && args[1]) { address = args[0] as EthereumAddress payload = args[1] } else if (apiPaths[method].hasAddress) { address = args[0] as EthereumAddress } else if (args[0]) { payload = args[0] as Payload<M> } }
当前痛点与疑问
可以看到buildUrl函数的类型定义和参数提取逻辑非常混乱,我想搞清楚以下问题:
- 是否存在更清晰的重构方式,提升代码可读性与维护性?
- 当前的API端点结构是否天然导致难以实现类型安全?
- 有没有更优雅的方法,基于条件类型提取可选参数?
- 这是不是典型的XY问题——我是不是从一开始就选了错误的解决方案?
尝试的函数重载方案
我试过用函数重载来实现,但这个方案的智能提示和自动补全效果很差,想知道这种方法是否真的更优:
type AddressMethods = keyof { [K in keyof ApiPaths as ApiPaths[K] extends { hasAddress: true } ? K : never]: 0 } type PayloadMethods = keyof { [K in keyof ApiMethods as Payload<K> extends never ? never : K]: 0 } // 仅需地址参数 function buildUrlWithOverloading<M extends Exclude<AddressMethods, AddressMethods & PayloadMethods>>(method: M, address: EthereumAddress): any // 仅需负载参数 function buildUrlWithOverloading<M extends Exclude<PayloadMethods, AddressMethods & PayloadMethods>>(method: M, payload: Payload<M>): any // 同时需要地址和负载参数 function buildUrlWithOverloading<M extends AddressMethods & PayloadMethods>(method: M, address: EthereumAddress, payload: Payload<M>): any function buildUrlWithOverloading<M extends keyof ApiMethods>(method: M, addressOrPayload?: EthereumAddress | Payload<M>, payload?: Payload<M>) { // 实现逻辑 }
内容的提问来源于stack exchange,提问作者Riccardo Perego
相关产品推荐
相关产品推荐

