Hvordan bygge en type fra enum-verdier i TypeScript?
Gitt følgende:
enum FooKeys {
FOO = 'foo',
BAR = 'bar',
}
Jeg vil lage et grensesnitt som dette, men i stedet for å definere nøkler manuelt, bygge det ut fra enum-verdiene.
interface Foo {
foo: string
bar: string
}
Er noe slikt mulig med TypeScript?
Takk!
Svar #1
Hvordan bygge en type fra enum-verdier i TypeScript?
En enum kan inneholde streng- og tallverdier.
For strenger: du kan lage en strengunion fra enum-verdier ved å bruke en malstreng
enum FooKeys {
FOO = 'foo',
BAR = 'bar',
}
type FooValues =`${FooKeys}`; // this equals 'foo' | 'bar'
For tall: vi kan enten lage strengunion på samme måte eller: fra og med TS 5.0
TypeScript 5.0 klarer å gjøre alle enums om til union-enums ved å lage en unik type for hvert beregnet medlem. Det betyr at alle enums nå kan snevres inn og få medlemmene sine referert til som typer også.
Som betyr:
enum MagicNumbers {
a = 1,
b = 42
}
const numberA : MagicNumbers = 1;
const numberB : MagicNumbers = 2; // will raise an error since TS 5.0
Eksperimenter selv
For begge: Ved å kombinere det ovenfor kan vi bygge en EnumAsUnion-typehjelper som følger
enum-som-union.ts
type StringValues<T> = {
[K in keyof T]: T[K] extends string ? T[K] : never;
}[keyof T];
type NumberValues<T> = {
[K in keyof T]: T[K] extends number ? T[K] : never;
}[keyof T];
/**
* Usage : type EnumValues = EnumAsUnion<typeof anEnum>
*/
type EnumAsUnion<T> = `${StringValues<T>}` | NumberValues<T>;
Eksempel :
import { EnumAsUnion } from 'enum-as-union';
enum anEnum {
val1 = 'a',
val2 = 'b',
val3 = 1,
val4 = 2,
}
type EnumValues = EnumAsUnion<typeof anEnum>;
let t: EnumValues;
t = 'a';
t = 'b';
t = 'c'; // error, as expected
t = 1;
t = 2;
t = 3; // error, as expected
Prøv det her
Svar #2
Ja, du kan bruke enum-verdier som nøkler. Og du kan bruke en mapped type som standardbibliotekets Record<K, V> for å forhindre gjentakelse:
enum FooKeys {
FOO = 'foo',
BAR = 'bar',
}
// probably all you need, but it's a type alias
type FooType = Record<FooKeys, string>;
// if you need an interface instead you can do this
interface FooInterface extends FooType {};
Og du kan verifisere at det fungerer:
declare const foo: FooInterface;
foo.foo; // okay
foo[FooKeys.FOO]; // okay
foo.bar; // okay
foo[FooKeys.BAR]; // okay
foo.baz; // error
Fungerer det for deg? Lykke til!
Svar #3
[@hackape sin løsning][1] er flott, men jeg fant minimal duplisering ved å utvide hans løsning som nedenfor:
type ReverseMap<T extends Record<keyof T, any>> = { [V in T[keyof T]]: { [K in keyof T]: T[K] extends V ? K : never; }[keyof T]; } const Map = { 'FOO': "foo" as "foo", 'BAR': "bar" as "bar", } const reverseMap: ReverseMap<typeof Map> = Object.entries(Map).reduce((rMap, [k, v]) => { rMap[v] = k; return rMap; }, {} as any); export type Values = keyof typeof reverseMap; // 'foo' | 'bar';Implementeringen av ReverseMap er godt forklart [her][2]
[1]: https://stackoverflow.com/a/60768453/5519365 [2]: https://stackoverflow.com/a/55209457/5519365
Oppdatering: Jeg fant en mye enklere løsning for ReverseMap
const Obj = {
FOO: 'foo',
BAR: 'bar',
} as const;
type ReverseMap<T> = T[keyof T];
export type Values = ReverseMap<typeof Obj>; // 'foo' | 'bar';
