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

基于条件类型提取可选参数的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 13:20:44