您当然可以在 TypeScript 中描述您所说的约束,尽管不是特定类型。相反,您可以将泛型(正如您提到的)与辅助函数一起使用,以便可以推断泛型类型参数而不是手动指定。
虽然 TypeScript 的类型系统确实不存在运行时,但类型系统的想法是描述 确实 在运行时存在的值集。指出您事先不知道颜色名称会是什么,这有点牵强附会。有一个实用程序类型,Record<K, V>,它的巨大用处并没有因为我现在不能告诉你哪些特定键将在K 中而减少。
强类型 ColorPalette 表示您的约束可能有用,也可能没有用,这取决于您是否认为值得在 TypeScript 代码周围拖动泛型类型参数。但这不同于说它没有用,因为类型系统被擦除了。
例如:
interface ColorPalette<K extends string> {
colors: {
[P in K]: string // associate a color name to a color (hex string)
};
defaultColorName: NoInfer<K> // should exist in colors above
}
type NoInfer<T> = [T][T extends any ? 0 : never]; // see microsoft/TypeScript#14829
const asColorPalette = <K extends string>(
colorPalette: ColorPalette<K>) => colorPalette;
在这里,我们将K 中的ColorPalette 泛型化为colors 属性的键的联合。原则上,您还希望 defaultColorName 的类型为 K,但这会使类型推断不太有用:理想情况下,您希望编译器使用 colors 来推断可用的颜色名称集,然后检查 defaultColorName 是其中之一。所以我们希望defaultColorName 成为K,但不要将其用于类型推断:NoInfer<K>。目前没有“官方”的方式来做到这一点;有关相关功能请求,请参阅 microsoft/TypeScript#14829。在该问题中,有几种适用于某些用例的解决方法/实现。以上我使用的是this one。
好的,所以ColorPalette<K> 使用K 作为colors 的键和defaultColorName 的值,当我们推断K 时,我们将只使用colors 而不是defaultColorName。然后我们有辅助函数asColorPalette(),它可用于将对象文字转换为ColorPalette<K>,以获得合适的K。如果有错误,那是因为违反了约束:
const okayColorPalette = asColorPalette({
colors: {
red: "#FF0000",
green: "#00FF00",
blue: "#0000FF"
},
defaultColorName: "red"
});
const badColorPalette = asColorPalette({
colors: {
red: "#FF0000",
green: "#00FF00",
blue: "#0000FF"
},
defaultColorName: "purple" // error!
//~~~~~~~~~~~~~~ <-- "purple" is not assignable to "red" | "green" | "blue"
});
const differentColorPalette = asColorPalette({
colors: {
harvestGold: "#E6A817",
avocado: "#568203",
burntOrange: "#BF5700"
},
defaultColorName: "avocado"
});
这里编译器接受okayColorPalette 和differentColorPalette 但拒绝badColorPalette。因此,如果编写了任何指定颜色名称的 TypeScript 代码,编译器会为您提供帮助。
即使您实际上从未在 TypeScript 代码中看到具体的颜色名称,ColorPalette<K> 类型仍然可以使用。大概您想编写一些 TypeScript 代码来操作 ColorPalette<K> 一些未知 K 的值,对吧?例如:
function useColorPalette<K extends string>(colorPalette: ColorPalette<K>) {
for (let k in colorPalette.colors) {
console.log(k + "->" + colorPalette.colors[k].toUpperCase());
}
console.log(
"The hex string corresponding to the default color is " +
colorPalette.colors[colorPalette.defaultColorName].toUpperCase()
);
colorPalette.colors.aquamarine; // error!
// Property 'aquamarine' does not exist on type '{ [P in K]: string; }'
}
由于泛型类型,编译器对colorPalette 有所了解:它知道for..in 循环产生一个k,可用于索引colors 属性;它知道defaultColorName 属性可以用作colors 属性的键;它知道像"aquamarine" 这样的随机字符串不一定可以用作colors 属性的键。如果您只使用string 而不是K extends string,编译器将允许aquamarine 索引。
同样,这对您来说可能不值得。泛型类型比特定类型更难处理。但这并不像“不要打扰,因为 TypeScript 在你运行任何东西之前就消失了”那么糟糕。
Playground link to code