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

基于字面量类型定义Partial Record键,解决Dex交换方法类型报错

DEX Swap方法实现的TypeScript类型安全问题解决

问题背景

我需要给所有已有的Dex抽象类实现准确的swap方法,目前写了如下TypeScript代码:

// 这是UniswapV2Router抽象类各swap方法处理器的联合输出类型
// 不同DEX的swap处理器返回值可能不同,未来会扩展其他DEX的对应类型
type FinderOutput = UniswapV2MethodOutput; 

// 这个类型定义了抽象类中带swap处理器的方法名,目前比较通用
// 未来想让它更具体化,能通过方法名识别swap方法类型
type ContractMethodImplementations = Record<string, (...args: any[]) => Promise<FinderOutput>>;

// 这个类型列出了所有要实现的DEX名称
type ContractNames = "UniswapV2" | "UniswapV3" | ...;

// 这个映射表关联DEX名称到对应的swap方法处理器,比如UniswapV2Router.swapExactETHForTokensHandler
const contractMethodsImplementations: Partial<Record<ContractNames, ContractMethodImplementations>> = {
  UniswapV2: {
    swapExactETHForTokensHandler: UniswapV2Router.swapExactETHForTokensHandler,
    // 其他方法
  },
  // 未来需要的其他DEX
};

因为还没完成所有实现,用了Partial<Record>,但即使通过if (contractMethodsImplementations[contractType])判断存在,TypeScript还是提示contractMethodsImplementations[contractType]可能为undefined,没法安全访问里面的方法。我想实现一个methodFinder函数,根据合约类型和方法名获取对应的swap方法处理函数,求更优的实现方案或解决办法:

export function methodFinder(
  tx: TransactionResponse,
  contractType: ContractNames,
  abi: InterfaceAbi,
  decodedData: TransactionDescription,
): FinderOutput {
  const { name: methodName, args: methodArgs, value: inputValue } = decodedData;

  if (contractMethodsImplementations[contractType]) {
    contractMethodsImplementations[contractType][methodName];
    // 这里要把参数传给对应的处理器并执行,返回结果
  }
}

解决方案

方案1:自定义类型守卫缩小类型范围

写一个自定义类型守卫,明确告知TypeScript当前contractType对应的方法映射是存在的,消除undefined警告:

function hasContractMethods(contractType: ContractNames): contractType is keyof typeof contractMethodsImplementations {
  return !!contractMethodsImplementations[contractType];
}

export async function methodFinder(
  tx: TransactionResponse,
  contractType: ContractNames,
  abi: InterfaceAbi,
  decodedData: TransactionDescription,
): Promise<FinderOutput> {
  const { name: methodName, args: methodArgs, value: inputValue } = decodedData;

  if (hasContractMethods(contractType)) {
    const methods = contractMethodsImplementations[contractType];
    const handler = methods[methodName];
    if (!handler) {
      throw new Error(`未找到合约 ${contractType} 中方法 ${methodName} 的处理器`);
    }
    // 根据实际处理器的参数要求传递参数
    return handler(tx, methodArgs, inputValue, abi);
  }
  throw new Error(`合约类型 ${contractType} 暂未实现`);
}

方案2:非空断言(谨慎使用)

如果能保证传入的contractType一定是已经实现的DEX类型,可以用非空断言!强制告诉TypeScript该值不为undefined:

export async function methodFinder(
  tx: TransactionResponse,
  contractType: ContractNames,
  abi: InterfaceAbi,
  decodedData: TransactionDescription,
): Promise<FinderOutput> {
  const { name: methodName, args: methodArgs, value: inputValue } = decodedData;

  const methods = contractMethodsImplementations[contractType]!;
  const handler = methods[methodName];
  if (!handler) {
    throw new Error(`未找到合约 ${contractType} 中方法 ${methodName} 的处理器`);
  }
  return handler(tx, methodArgs, inputValue, abi);
}

注意:该方案风险较高,若后续新增ContractNames但未实现对应方法映射,会触发运行时错误。

方案3:拆分类型,区分已实现与待实现DEX

将ContractNames拆分为已实现和待实现两个类型,让方法映射表只对应已实现的DEX,彻底避免Partial带来的undefined问题:

// 已实现的DEX类型
type ImplementedContractNames = "UniswapV2";
// 所有需要实现的DEX类型
type ContractNames = ImplementedContractNames | "UniswapV3" | ...;

// 无需Partial,只映射已实现的DEX
const contractMethodsImplementations: Record<ImplementedContractNames, ContractMethodImplementations> = {
  UniswapV2: {
    swapExactETHForTokensHandler: UniswapV2Router.swapExactETHForTokensHandler,
    // 其他方法
  },
};

export async function methodFinder(
  tx: TransactionResponse,
  contractType: ContractNames,
  abi: InterfaceAbi,
  decodedData: TransactionDescription,
): Promise<FinderOutput> {
  const { name: methodName, args: methodArgs, value: inputValue } = decodedData;

  if (contractType in contractMethodsImplementations) {
    const methods = contractMethodsImplementations[contractType as ImplementedContractNames];
    const handler = methods[methodName];
    if (!handler) {
      throw new Error(`未找到合约 ${contractType} 中方法 ${methodName} 的处理器`);
    }
    return handler(tx, methodArgs, inputValue, abi);
  }
  throw new Error(`合约类型 ${contractType} 暂未实现`);
}

这个方案更严谨,能在编译阶段就区分已实现和未实现的DEX,减少运行时错误。

补充优化:精细化方法名类型匹配

如果想进一步提升类型安全性,可以给每个DEX定义专属的方法名类型,替换通用的string,让TypeScript检查方法名的合法性:

// UniswapV2专属方法名类型
type UniswapV2MethodNames = "swapExactETHForTokensHandler" | "swapExactTokensForETHHandler";
// UniswapV3专属方法名类型
type UniswapV3MethodNames = "swapExactInputHandler" | ...;

// 关联DEX类型到对应的方法名和处理器类型
type ContractMethodMap = {
  UniswapV2: Record<UniswapV2MethodNames, (...args: any[]) => Promise<FinderOutput>>;
  UniswapV3: Record<UniswapV3MethodNames, (...args: any[]) => Promise<FinderOutput>>;
};

const contractMethodsImplementations: Partial<ContractMethodMap> = {
  UniswapV2: {
    swapExactETHForTokensHandler: UniswapV2Router.swapExactETHForTokensHandler,
    // 其他方法
  },
};

这样TypeScript会自动校验方法名是否属于对应DEX的合法方法,避免拼写错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 08:15:09