使用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,结果为booleanname:TProvaUnnamed无name(即undefined)、TProvaNamed是string,结果为string | undefinedhandler:两个成员的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
相关产品推荐
相关产品推荐

