TypeScript“字符串文字”子集不能分配给重载函数中的完整集

Posted

技术标签:

【中文标题】TypeScript“字符串文字”子集不能分配给重载函数中的完整集【英文标题】:TypeScript `string literal` subsets not assignable to full set in overloaded function 【发布时间】:2020-09-30 11:06:53 【问题描述】:

问题

尝试为类型化的键值对创建映射函数:

type Color = 
  "Red": number,
  "Green": string
  "Yellow": boolean
  "Purple": object
;
type PropMapping = 
  "Apple": Pick<Color, "Red" | "Green">,
  "Banana": Pick<Color, "Yellow" | "Green">,
  "Grape": Pick<Color, "Purple" | "Green">


function map(key: "Apple"): Pick<Color, "Red" | "Green">;
function map(key: "Banana"): Pick<Color, "Yellow" | "Green">;
function map(key: "Grape"): Pick<Color, "Purple" | "Green">;
function map(key: keyof PropMapping): Partial<Color> 
  // perform actual mapping in here (e.g. via switch-case)
  return ;

稍后我尝试使用此映射函数,但使用 Color 类型的子集

// type of fruits is a subset of `keyof PropMapping`
const fruits: (keyof Pick<PropMapping, "Apple" | "Banana">)[] = ["Apple", "Banana", "Banana"];

// call the mapping function on each fruit
const colors: Partial<Color>[] = fruits.map(fruit => map(fruit));

但是,在调用 map(fruit) 时,我收到以下错误:

No overload matches this call.
  The last overload gave the following error.
    Argument of type '"Apple" | "Banana"' is not assignable to parameter of type '"Grape"'

Type '"Apple"' is not assignable to type '"Grape"'.

对我来说似乎很奇怪,因为 fruit 参数是可以传入的可能结果的子集。无法使此映射函数重载工作不使用 as 关键字(需要as 对我来说有点代码味道)。

最终目标

我希望能够从一个或多个Partial&lt;Color&gt;s 重构Color。例如,假设我这样做:

const redGreen = map("Apple");
const yellowGreen = map("Banana");
const purpleGreen = map("Grape");

const allColors: Color = 
  ...redGreen,
  ...yellowGreen,
  ...purpleGreen
;

上面的工作是因为 TypeScript 可以推断出聚合的 Color 对象的属性都在那里。但是,我想保持聚合 Partial&lt;Color&gt; 对象的能力,如上例所示:

const colors: Partial<Color>[] = fruits.map(fruit => map(fruit));

这是让我崩溃的部分

编辑

编辑:Color 更改为一个对象,以更好地模拟我的情况(并使其他人更容易复制粘贴到他们自己的 IDE 中

EDIT2: 修复重载返回类型

EDIT3: (keyof Pick&lt;PropMapping, "Apple" | "Banana"&gt;)[]...天哪。感谢大家指出这些

【问题讨论】:

Pick&lt;Color, "Apple" | "Banana"&gt; 无效,因为Pick 选择属性,但Colorstring 的子类型,没有"Apple""Banana" 属性。请考虑修改上述代码以构成适合放入独立 IDE 的minimal reproducible example,以便其他人可以自己演示该问题。祝你好运! 哎呀。真实真实。试图在不直接显示代码的情况下对我的实际用例进行建模。会修改 请注意Pick&lt;Color, "Apple" | "Banana"&gt;仍然无效,因为Color的属性不同???? @buuchan 你的意思是const fruits: (keyof Pick&lt;PropMapping, "Apple" | "Banana"&gt;)[] @AlexWayne 是的。谢谢! 【参考方案1】:

TypeScript 不支持同时调用多个重载签名。可以想象,编译器可以通过允许参数的联合然后返回返回类型的联合来支持解决此类调用,但这不是一个特性,至少现在是这样。有关此类支持的公开建议,请参阅 microsoft/TypeScript#14107。

除非并在此得到支持之前,如果您希望编译器理解此类调用,则需要重构您的代码。您可以为每个可能的预期参数集添加调用签名,但这仅适用于少数初始重载集,因为如果您从 n 不相交的调用签名开始你最终会得到 2n 个合并的签名。


对于您发布的将键映射到值类型的特定示例代码,到目前为止,最直接的解决方案是放弃重载(或至少放弃多个调用签名)以支持 generic 函数,例如这个:

function map2<K extends keyof PropMapping>(key: K): PropMapping[K];
function map2(key: keyof PropMapping): Color 
    return null!;

这里,map2 接受一个泛型类型 K constrained 的 key 参数作为您的 PropMapping 类型的键,它返回 PropMapping[K],即当您获取值的类型时look up K 键在 PropMapping 对象中。

那么你的数组调用应该可以工作了:

const colors = fruits.map(fruit => map2(fruit));
// const colors: (Pick<Color, "Red" | "Green"> | Pick<Color, "Green" | "Yellow">)[]

好的,希望对您有所帮助;祝你好运!

Playground link to code

【讨论】:

谢谢!是的,那个 GitHub 线程看起来基本上就像我要求的那样。但是,我可以通过添加一个额外的(和冗余的)重载来使其工作:function map(key: keyof PropMapping): Partial&lt;Color&gt;; 在函数实现之前 是的,如果您可以接受 colors 超出必要的宽度(例如,在上面,"Purple" 不在返回类型中,因为您没有传入 "Grape" ) 确实如此。重构/推断部分聚合的Pick&lt;Color, T&gt; 是有限的(正如您所指出的)。幸运的是,我只需要区分“更广泛”的Partial&lt;Color&gt; 和完整的Color 类型。

以上是关于TypeScript“字符串文字”子集不能分配给重载函数中的完整集的主要内容,如果未能解决你的问题,请参考以下文章

如何检查字符串文字类型是不是包含 TypeScript 中的值?

TypeScript数组到字符串文字类型

如何从 Typescript 中的常量定义字符串文字联合类型

如何使 TypeScript 字符串枚举适用于字符串文字并正确进行类型推断

TypeScript - 'String' 类型的参数不能分配给'string' 类型的参数

Typescript类型'string'不能分配给类型