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

如何让TypeScript在保留显式类型时推断泛型方法参数类型

TypeScript 类型校验问题解决方案

问题复现

以下代码会触发类型不匹配报错:

const variant1 = (data: { hello: number }) => {/* Do some work here... */};
const variant2 = (data: { hi: string }) => {/* Do some work here... */};

interface Data<T = unknown> {
  callback: (data: T) => void;
}

const f: Record<string, Data> = {
  name1: { callback: variant1 },
  name2: { callback: variant2 },
  // 报错:'(data: {hello: number}) => void' is not assignable to type 'Data<unknown>'
}

如果你定义的Data是直接的函数类型(type Data<T = unknown> = (data: T) => void),报错逻辑完全一致,只需要去掉示例里的callback包裹层即可。

报错原因

TypeScript 函数参数遵循逆变规则:

  • 类型(data: unknown) => void要求函数可以接收任意类型的入参
  • variant1/variant2只能接收固定结构的参数,无法满足任意入参的要求,因此不能赋值给Data<unknown>位置
  • 显式标注Record<string, Data>等价于Record<string, Data<unknown>>,会强制所有值的泛型参数为统一的unknown,无法单独推导每个key对应的实际类型。

实现方案

方案1:兼容TS4.7,保留显式标注

如果不需要严格的参数类型校验,可以修改Data的默认泛型参数为any,不需要改动f的显式标注即可通过校验:

interface Data<T = any> {
  callback: (data: T) => void;
}

const f: Record<string, Data> = {
  name1: { callback: variant1 }, // 校验通过
  name2: { callback: variant2 }, // 校验通过
}

注意:该方案会丢失部分类型安全,调用callback时传入错误参数不会触发报错。

方案2:保留完整类型安全(TS4.7可用)

如果需要保留每个方法的参数类型推导,同时约束所有值符合Data结构,可以通过泛型辅助函数实现,不需要改动类型约束逻辑:

// 泛型辅助函数:校验入参符合Data结构,同时保留每个key的具体类型
function defineDataMap<T extends Record<keyof T, Data<any>>>(map: T): T {
  return map;
}

const f = defineDataMap({
  name1: { callback: variant1 }, // 自动推导为Data<{hello: number}>
  name2: { callback: variant2 }, // 自动推导为Data<{hi: string}>
});

方案3:TS4.9+ 原生实现

升级到TypeScript 4.9及以上版本,可以用satisfies运算符直接实现类型校验+类型保留,不需要辅助函数:

const f = {
  name1: { callback: variant1 },
  name2: { callback: variant2 },
} satisfies Record<string, Data>;

注意

如果强制要求f必须通过冒号显式标注为Record<string, Data>(即固定为Data<unknown>),在TS现有类型规则下无法保留每个key的具体参数类型——显式标注会将所有值拓宽为统一类型,必然丢失单个条目的泛型信息。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 07:54:33