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

使用Pick后TypeScript类型推断丢失的原因及解决办法

先还原问题场景代码

type TProvaUnnamed<T> = {
  value: T;
  handler: (args: { value: T }) => void;
};

type TProvaNamed<T> = {
  value: T;
  name: string;
  handler: (args: { value: T; name: string }) => void;
};

type TProva<T> = TProvaUnnamed<T> | TProvaNamed<T>;

// 正常工作,能正确推断handler参数类型
function prova<T>(opts: TProva<T>) {
  if ('name' in opts) {
    opts.handler({ value: opts.value, name: opts.name });
  } else {
    opts.handler({ value: opts.value });
  }
}

// 出现问题的场景
type TProvaBool = Pick<TProva<boolean>, 'value' | 'name' | 'handler'>;

function prova_bool(opts: TProvaBool) {
  opts.handler({ value: opts.value }); // 编译错误:参数不能赋给类型never
}
类型推断丢失的原因

原生Pick<T, K>的逻辑是对整个类型T提取指定属性,当T是联合类型时,TypeScript会先把联合所有成员的目标属性做交叉合并,而非对每个成员单独提取后再组成新联合。

放到你的场景里:

  • TProva<boolean>是TProvaUnnamed<boolean> | TProvaNamed<boolean>两个成员的联合
  • 用Pick提取属性后,TypeScript会分别计算每个属性的交叉类型:
    • value:两个成员都是boolean,结果为boolean
    • name:TProvaUnnamed无name(即undefined)、TProvaNamed是string,结果为string | undefined
    • handler:两个成员的handler参数分别是{ value: boolean }和{ value: boolean; name: string },交叉这两个函数类型时,TypeScript会取参数的交集——没有任何值能同时满足这两个参数的严格要求,最终handler的参数类型变成never,导致编译错误。

核心就是:Pick破坏了原联合类型中属性间的绑定关联,把原本配对的name和handler参数类型拆开了,TypeScript无法再识别它们的对应关系。

解决办法

1. 直接复用原联合类型

如果只是需要用到value、name、handler这几个属性,直接使用TProva<boolean>即可,不用额外做Pick,保留联合类型的完整结构:

function prova_bool(opts: TProva<boolean>) {
  if ('name' in opts) {
    opts.handler({ value: opts.value, name: opts.name });
  } else {
    opts.handler({ value: opts.value });
  }
}

2. 自定义保留联合结构的Pick工具类型

利用TypeScript条件类型的分布式特性,实现一个对联合每个成员单独执行Pick的工具类型,避免交叉合并:

// 对联合类型的每个成员单独应用Pick,再重新组成联合
type PickPreserveUnion<T, K extends keyof T> = T extends any ? Pick<T, K> : never;

// 此时TProvaBool是两个成员的联合,而非交叉后的单一类型
type TProvaBool = PickPreserveUnion<TProva<boolean>, 'value' | 'name' | 'handler'>;

function prova_bool(opts: TProvaBool) {
  if ('name' in opts) {
    opts.handler({ value: opts.value, name: opts.name });
  } else {
    opts.handler({ value: opts.value });
  }
}

3. 手动重建带关联的联合类型

如果必须定义新类型,可以手动复刻原联合的结构,明确保留属性间的绑定关系:

type TProvaBool = 
  | { value: boolean; handler: (args: { value: boolean }) => void }
  | { value: boolean; name: string; handler: (args: { value: boolean; name: string }) => void };

function prova_bool(opts: TProvaBool) {
  // 正常推断handler参数
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 20:32:37