如何强制接口在打字稿 3.0 中“实现”枚举的键?

pol*_*oli 5 types strong-typing typescript

假设我有一些 enum E { A = "a", B = "b"}。我想强制某些接口或类型(为了可读性,我只提到接口)具有 E 的所有键。但是,我想分别指定每个字段的类型。因此, { [P in E]: any }甚至{ [P in E]: T }都不是正确的解决方案。

例如,代码可能包含两个实现 E 的接口:

  • E { A = "a", B = "b"}
  • Interface ISomething { a: string, b: number}
  • Interface ISomethingElse { a: boolean, b: string}

随着 E 在开发过程中的扩展,它可能会变成:

  • E { A = "a", B = "b", C="c"}
  • Interface ISomething { a: string, b: number, c: OtherType}
  • Interface ISomethingElse { a: boolean, b: string, c: DiffferntType}

几个小时后:

  • E { A = "a", C="c", D="d"}
  • Interface ISomething { a: string, c: ChosenType, d: CarefullyChosenType}
  • Interface ISomethingElse { a: boolean, c: DiffferntType, d: VeryDifferentType}

等等等等。因此,从https://www.typescriptlang.org/docs/handbook/advanced-types.html看来,它还不受支持。有没有我遗漏的打字稿黑客?

jca*_*alz 11

我猜你已经致力于写出 theenum和 the interface,然后希望 TypeScript 会警告你interface缺少的键enum(或者如果它有额外的键)?

假设你有

enum E { A = "a", B = "b", C="c"};
interface ISomething { a: string, b: number, c: OtherType};
Run Code Online (Sandbox Code Playgroud)

您可以使用条件类型让 TypeScript 找出E的键中是否缺少 的任何成分ISomething

type KeysMissingFromISomething = Exclude<E, keyof ISomething>;
Run Code Online (Sandbox Code Playgroud)

never如果您没有缺少任何密钥,则应该是这种类型ISomething。否则,它将是Elike的值之一E.C

您还可以让编译器弄清楚是否ISomething有任何不是组成部分的键E,也可以使用条件类型......尽管这更复杂,因为您无法enum以预期的方式以编程方式操作s。这里是:

type ExtraKeysInISomething = { 
  [K in keyof ISomething]: Extract<E, K> extends never ? K : never 
}[keyof ISomething];
Run Code Online (Sandbox Code Playgroud)

同样,never如果你没有额外的钥匙,这将是。然后,您可以never通过使用泛型约束默认类型参数来强制编译时错误,如果其中之一不是:

type VerifyISomething<
  Missing extends never = KeysMissingFromISomething, 
  Extra extends never = ExtraKeysInISomething
> = 0;
Run Code Online (Sandbox Code Playgroud)

类型VerifyISomething本身并不有趣(它总是0),但是如果它们各自的默认值不是,泛型参数MissingExtra会给你错误never

让我们试试看:

enum E { A = "a", B = "b", C = "c" }
interface ISomething { a: string, b: number, c: OtherType }
type VerifyISomething<
  Missing extends never = KeysMissingFromISomething,
  Extra extends never = ExtraKeysInISomething
  > = 0; // no error
Run Code Online (Sandbox Code Playgroud)

enum E { A = "a", B = "b", C = "c" }
interface ISomething { a: string, b: number } // oops, missing c
type VerifyISomething<
  Missing extends never = KeysMissingFromISomething, // error!
  Extra extends never = ExtraKeysInISomething
  > = 0; // E.C does not satisfy the constraint
Run Code Online (Sandbox Code Playgroud)

enum E { A = "a", B = "b", C = "c" }
interface ISomething { a: string, b: number, c: OtherType, d: 1} // oops, extra d
type VerifyISomething<
  Missing extends never = KeysMissingFromISomething,
  Extra extends never = ExtraKeysInISomething // error!
  > = 0; // type 'd' does not satisfy the constraint
Run Code Online (Sandbox Code Playgroud)

所以所有这些都有效......但它并不漂亮。


一种不同的hacky方法是使用一个dummy,class它的唯一目的是在你没有添加正确的属性时责骂你:

enum E { A = "a", B = "b" , C = "c"};
class CSomething implements Record<E, unknown> {
  a!: string;
  b!: number;
  c!: boolean;
}
interface ISomething extends CSomething {}
Run Code Online (Sandbox Code Playgroud)

如果您遗漏了其中一个属性,则会出现错误:

class CSomething implements Record<E, unknown> { // error!
  a!: string;
  b!: number;
}
// Class 'CSomething' incorrectly implements interface 'Record<E, unknown>'.
// Property 'c' is missing in type 'CSomething'.
Run Code Online (Sandbox Code Playgroud)

它不会警告您有关额外属性的信息,尽管您可能不在乎?


无论如何,希望其中之一对您有用。祝你好运。