TypeScript 接口可以要求多个字符串键或值相同吗?
Posted
技术标签:
【中文标题】TypeScript 接口可以要求多个字符串键或值相同吗?【英文标题】:Can a TypeScript interface require multiple string keys or values to be the same? 【发布时间】:2021-04-26 10:38:56 【问题描述】:假设我想创建一个看起来像这样的interface
:
interface ColorPalette
defaultColorName: string // should exist in colors below
colors:
[colorName: string]: string // associate a color name to a color (hex string)
换句话说,这个调色板概念允许指定颜色列表(其中每种颜色都可以通过任意颜色名称访问),但也提供默认颜色。
我想知道在 TypeScript 中是否可以强制 defaultColorName
的值存在于键/值对象 colors
中,因为指定不存在于colors
.
我知道如果颜色名称是一个字符串联合,例如type ColorType = 'bright' | 'dark' | 'subtle'
,我可以在其中使用泛型和interface
,例如interface ColorPalette<T extends ColorType>
。在这种情况下,颜色名称是任意的string
s,可以是任何东西。
我正在使用调色板的概念来演示我想要做什么,我的实际用例并不特定于颜色。
【问题讨论】:
如果他们在编译时不知道,不,你不能 【参考方案1】:这里的关键因素是:
在这种情况下,颜色名称可以是任意字符串。
由于这个规定,您要求的是不可能,因为您要求 TypeScript 编译器(令人惊讶的是仅在编译时存在的东西)根据以下内容做出决定在运行时发生(将任意字符串键/值对插入colors
对象)。
【讨论】:
我怀疑是这种情况,谢谢解释和确认!【参考方案2】:Typescript 类型安全仅在编译时。因此,编译器无法验证编译时未知的任何内容。
此外,如果没有明确的运行时检查,您甚至无法保证在编译时可能完全有效的所有假设在运行时仍然有效。想象一下,您正在从文件或 API 中读取 JSON 数据,并期望该数据遵循某个接口。但是如果没有明确的运行时检查,您可能会遇到各种运行时错误或意外/未定义的行为。例如,属性可能丢失或类型错误。
【讨论】:
【参考方案3】:您当然可以在 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
【讨论】:
以上是关于TypeScript 接口可以要求多个字符串键或值相同吗?的主要内容,如果未能解决你的问题,请参考以下文章