简体   繁体   English

TypeScript 接口可以要求多个字符串键或值相同吗?

[英]Can a TypeScript interface require multiple string keys or values to be the same?

Let's say I want to create an interface that looks something like this:假设我想创建一个看起来像这样的interface

interface ColorPalette {
  defaultColorName: string // should exist in colors below
  colors: {
    [colorName: string]: string // associate a color name to a color (hex string)
  }
}

In other words, this color palette concept allows specifying a list of colors (where each can be accessed by an arbitrary color name), but also providing a default color.换句话说,此调色板概念允许指定 colors 列表(其中每个都可以通过任意颜色名称访问),但也提供默认颜色。

I'm wondering if it is possible in TypeScript to enforce that the value of defaultColorName is present in the key/value object colors , as it would not make sense to specify a default color that does not exist in colors . I'm wondering if it is possible in TypeScript to enforce that the value of defaultColorName is present in the key/value object colors , as it would not make sense to specify a default color that does not exist in colors .

I know how this could work if the color names are a string union, for example type ColorType = 'bright' | 'dark' | 'subtle'如果颜色名称是一个字符串联合,我知道这会如何工作,例如type ColorType = 'bright' | 'dark' | 'subtle' type ColorType = 'bright' | 'dark' | 'subtle' type ColorType = 'bright' | 'dark' | 'subtle' where I could then use generics with the interface , for example interface ColorPalette<T extends ColorType> . type ColorType = 'bright' | 'dark' | 'subtle' ,然后我可以将 generics 与interface一起使用,例如interface ColorPalette<T extends ColorType> In this case, the color names are arbitrary string s that could be anything.在这种情况下,颜色名称是任意string s,可以是任何东西。

I'm using the concept of a color palette to demonstrate what I want to do, my actual use case is not specific to colors.我正在使用调色板的概念来演示我想要做什么,我的实际用例并不特定于 colors。

The key factor here is this:这里的关键因素是:

In this case, the color names are arbitrary strings that could be anything.在这种情况下,颜色名称是可以是任意字符串的任意字符串。

Because of this stipulation, what you are asking for is not possible , because you are asking the TypeScript compiler (something that only exists at compile-time, surprisingly) to make decisions based on something that happens at runtime (inserting arbitrary strings key/value pairs into the colors object).由于此规定,您所要求的内容是不可能的,因为您要求 TypeScript 编译器(令人惊讶的是仅在编译时存在的东西)根据运行时发生的事情(插入任意字符串键/值)做出决定配对到colors对象)。

You certainly can describe the constraint you're talking about in TypeScript, although not as a specific type.您当然可以在 TypeScript 中描述您正在谈论的约束,尽管不是特定类型。 Instead, you can use generics (as you mention) along with helper functions so that generic type parameters can be inferred instead of manually specified.相反,您可以使用 generics (正如您提到的)以及辅助函数,以便可以推断泛型类型参数而不是手动指定。

While it is true that TypeScript's type system does not exist runtime, the idea of the type system is to describe sets of values that do exist at runtime.虽然 TypeScript 的类型系统确实不存在运行时,但类型系统的想法是描述运行时确实存在的值集。 It's a bit of a red herring to point out that you don't know in advance what the color names will be.指出您事先不知道颜色名称会是什么,这有点牵强附会。 There's a utility type, Record<K, V> , whose immense usefulness is not lessened by the fact that I can't tell you right now which specific keys will be in K .有一个实用程序类型Record<K, V> ,它的巨大用处并没有因为我现在不能告诉你哪些特定键将在K中而减少。

It might or might not be useful to strongly type ColorPalette to represent your constraint, depending on whether you think it's worth dragging generic type parameters around your TypeScript code.强类型ColorPalette表示您的约束可能有用也可能没有用,这取决于您是否认为值得在 TypeScript 代码周围拖动泛型类型参数。 But that's different from saying that it's not useful because the type system is erased.但这不同于说它没有用,因为类型系统被擦除了。


For example:例如:

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;

Here we are making ColorPalette generic in K , the union of keys of the colors property.在这里,我们在K ColorPalette通用,这是colors属性的键的联合。 In principle you also want defaultColorName to be of type K , but this will make type inference less useful: ideally you want the compiler to use colors to infer the set of color names available, and then just check that defaultColorName is one of them.原则上,您还希望defaultColorName的类型为K ,但这会使类型推断不太有用:理想情况下,您希望编译器使用colors推断可用的颜色名称集,然后检查defaultColorName是否是其中之一。 So we want defaultColorName to be K but not to use it for type inference: NoInfer<K> .所以我们希望defaultColorNameK但不将其用于类型推断: NoInfer<K> There is currently no "official" way to do this;目前没有“官方”的方式来做到这一点; see microsoft/TypeScript#14829 for the relevant feature request.有关相关功能请求,请参阅microsoft/TypeScript#14829 In that issue are several workarounds/implementations that work for some use cases.在该问题中,有几种适用于某些用例的解决方法/实现。 Above I'm using this one .上面我正在使用这个

Okay, so ColorPalette<K> uses K for both the keys of colors and the value of defaultColorName , and when we infer K we will only use colors and not defaultColorName .好的,所以ColorPalette<K>K用于colors的键和defaultColorName的值,当我们推断K时,我们将只使用colors而不是defaultColorName Then we have the helper function asColorPalette() which can be used to turn an object literal into a ColorPalette<K> for a suitable K .然后我们有助手 function asColorPalette()可用于将 object 文字转换为ColorPalette<K>以获得合适的K If there are errors, it's because the constraint has been violated:如果有错误,那是因为违反了约束:

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"
});

Here the compiler accepts okayColorPalette and differentColorPalette but rejects badColorPalette .在这里,编译器接受okayColorPalettedifferentColorPalette但拒绝badColorPalette So if any TypeScript code ever gets written in which the color names are specified, the compiler will help you.因此,如果任何 TypeScript 代码被写入其中指定了颜色名称,编译器将帮助您。


Even if you never actually see concrete color names in TypeScript code, the ColorPalette<K> type can still be of use.即使您从未真正在 TypeScript 代码中看到具体的颜色名称, ColorPalette<K>类型仍然可以使用。 Presumably you want to write some TypeScript code to manipulate ColorPalette<K> values for some unknown K , right?大概您想编写一些 TypeScript 代码来操作ColorPalette<K>某些未知K的值,对吧? For example:例如:

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; }'
}

The compiler knows a bit about colorPalette due to the generic typing: it knows that the for..in loop produces a k that can be used to index into the colors property;由于泛型类型,编译器对colorPalette了解:它知道for..in循环产生一个k可用于索引colors属性; it knows that the defaultColorName property can be used as a key of the colors property;它知道defaultColorName属性可以作为colors属性的键; and it knows that some random string like "aquamarine" cannot necessarily be used as a key of the colors property.它知道像"aquamarine"这样的随机字符串不一定可以用作colors属性的键。 If you just used string instead of K extends string , the compiler would have allowed that aquamarine index.如果您只是使用string而不是K extends string ,编译器将允许该aquamarine索引。


Again, this might not be worth it to you.同样,这对您来说可能不值得。 Generic types are more cumbersome to deal with than specific types.泛型类型比特定类型更难处理。 But it's not quite as bad as "don't bother because TypeScript goes away before you run anything".但这并不像“不要打扰,因为 TypeScript 在你运行任何东西之前就消失了”那么糟糕。

Playground link to code Playground 代码链接

Typescript typesafety is compile time only. Typescript 类型安全仅在编译时。 Thus the compiler can't verify anything that's not known at compiletime.因此,编译器无法验证编译时未知的任何内容。

Furthermore, without explicit runtimechecks, you can't even guarantee, all assumptions that may be perfectly valid at compiletime are still valid at runtime.此外,如果没有明确的运行时检查,您甚至无法保证在编译时可能完全有效的所有假设在运行时仍然有效。 Just imagine you are reading JSON data from a file or an API and expect that data to follow a certain interface.想象一下,您正在从文件或 API 读取 JSON 数据,并期望该数据遵循某个接口。 But without explicit runtime checking, you may get various runtime errors or unexpected/undefined behaviour.但是如果没有明确的运行时检查,您可能会遇到各种运行时错误或意外/未定义的行为。 For instance the properties may be missing, or be of wrong type.例如,属性可能丢失,或者类型错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM