Typescript: 从枚举创建字符串字面量联合类型

142

我希望从一个枚举中获取一个字符串文字联合类型。

对于这个枚举...

enum Weekday {
    MONDAY = 'mon',
    TUESDAY = 'tue',
    WEDNESDAY = 'wed'
}

我想要这个:

type WeekdayType = 'mon' | 'tue' | 'wed';

我尝试了typeof keyof Weekday,但结果是'MONDAY' | 'TUESDAY' | 'WEDNESDAY'。感觉解决方案可能涉及到映射类型,但我似乎无法理解。

我该如何做?


3
很遗憾,你不能这样做。更多信息请参见这个相关问题 - jcalz
真不幸! - AKG
@jcalz 当人们像你这样在评论中“回答”问题时,社区无法编辑这些答案。这真是遗憾,因为现在这个问题已经可以解决了,但是进入这个问题的人首先会阅读到你(当时正确,现在不正确)的评论,可能会因此被误导。 - Steve Dodier-Lazaro
4个回答

203

请参见TS4.1 ANSWER

type WeekdayType = `${Weekday}`;

预 TS-4.1 答案:

这不能程序化地完成...您正在尝试将类型 WeekdayWeekday.MONDAY | Weekday.TUESDAY | Weekday.WEDNESDAY)转换为类型 WeekdayType"mon" | "tue" | "wed")。这种转换是一种 扩大,因为WeekdayWeekdayType的子类型:

type WeekdayExtendsWeekdayType = 
  Weekday extends WeekdayType ? true : false
// type WeekdayExtendsWeekdayType = true

很不幸,编译器没有为您提供从枚举类型中去除“枚举”属性并使其成为普通文字类型的句柄。


那么,解决方法呢?也许您实际上并不需要一个枚举,而是可以使用一个属性值为字符串字面量的对象:

const lit = <V extends keyof any>(v: V) => v;
const Weekday = {
  MONDAY: lit("mon"),
  TUESDAY: lit("tue"),
  WEDNESDAY: lit("wed")
}
type Weekday = (typeof Weekday)[keyof typeof Weekday],
如果您检查它,名为Weekday就像一个枚举对象:
console.log(Weekday.TUESDAY); // tue

尽管名为Weekdaytype表现得像字符串值"mon" | "tue" | "wed"的联合体,而你之前将其称为WeekdayType

const w: Weekday = "wed"; // okay
const x: Weekday = "xed"; // error

因此,在这个解决方法中,没有“枚举”的特性,因此无需区分类型Weekday和类型WeekdayType。这与实际的enum略有不同(包括像Weekday.MONDAY这样的类型,你必须将其表示为繁琐的typeof Weekday.MONDAY或为其创建不同的类型别名),但它可能表现得足够相似以便于使用。对你有用吗?


9
更新:自 TypeScript 3.4 起,不再需要特殊的 lit 函数。你可以使用常量断言代替。 - just-boris
好奇的问题 @jcalz。<V extends keyof any>是什么意思?它防止任何类型的字符串/数字字面量扩展吗?谢谢! - hotell
有没有一种方法可以做相反的事情?从一个包含字符串字面量联合类型中获取一个字符串枚举? - Pablo K
1
@Pablo 我不这么认为。 - jcalz
2
type WeekdayType = `${Weekday}`; 真是太棒了。 - Touré Holder

70

TypeScript 4.1+:

如上所述,可以使用模板文字类型来实现:

type WeekdayType = `${Weekday}`;

TypeScript 3.4+:

在 @jcalz 的回答和 @just-boris 的评论之后,这里有一个使用 const 断言的示例:

const Weekday = {
  MONDAY: "mon",
  TUESDAY: "tue",
  WEDNESDAY: "wed",
} as const;

type Weekday = (typeof Weekday)[keyof typeof Weekday];

编辑:

我为那些想深入了解的人撰写了一篇博客文章


1
哦,我的天啊,谢谢你。我已经挣扎了好几个小时,试图弄清楚这个问题。 - Seth Bro
应该可以工作,但有限制。type Weekday1 = (typeof Weekday)[keyof typeof Weekday]; type Weekday2 = `${Weekday}`; const day1: Weekday1 = Weekday.MONDAY; => 可以工作 const day2: Weekday2 = Weekday.MONDAY; => 可以工作 const day3: Weekday1 = 'mon' => 不起作用 const day4: Weekday2 = 'mon'; => 可以工作 - Mina Luke

56

使用Typescript 4.1,这可以实现!

enum Weekday {
  MONDAY = 'mon',
  TUESDAY = 'tue',
  WEDNESDAY = 'wed'
}

type WeekdayType = `${Weekday}`;

对于数字枚举,感谢@okku的贡献:

enum ThreeDigits {
  ZERO = 0,
  ONE = 1,
  TWO = 2
}

type ThreeDigitsType = `${ThreeDigits}` extends `${infer T extends number}` ? T : never;

4
更简单的方法是:输入 WeekdayUnion = ${Weekday}; 就行了! - Nonoroazoro
1
你有没有运用 ts-node 编译成功呢? - Michael G
2
@MichaelG 是的,你只需要在你项目的 package.json 文件中更新你的 TypeScript 版本到 4.1,在你的开发依赖项中。 - coyotte508
4
有人能提供这种行为的文档吗? - sandbox992
1
它可以与数字枚举一起使用,但需要进行一些额外的工作: type WeekdayType = ${Weekday}extends${infer T extends number} ? T : never; - Okku
显示剩余2条评论

0
Typescript 4.8+有一些新功能,我们可以在这里使用。它可以处理字符串枚举、数字枚举和混合枚举。

Typescript Playground

export type EnumToPrimitiveUnion<T> = `${T & string}` | ParseNumber<`${T & number}`>;
type ParseNumber<T> = T extends `${infer U extends number}` ? U : never;

export function typeCastEnum<E>(value: EnumToPrimitiveUnion<E>): E {
  return value as E;
}

enum Enum1 {
  A = 'a',
  B = 'b',
  X = 5,
}
enum Enum2 {
  A = 'a',
  C = 'c',
  Y = 6,
}
enum Enum3 {
  A2 = 'a',
  B2 = 'b',
  X2 = 5,
}


type PrimitiveEnum1 = EnumToPrimitiveUnion<Enum1>; // 'a' | 'b' | 5
typeCastEnum<Enum1>(Enum2.A); // ok, 'a' fits
typeCastEnum<Enum1>('b'); // ok
typeCastEnum<Enum1>(5); // ok
typeCastEnum<Enum1>(Enum3.A2 as Enum3); // ok, all values of Enum3 fit

// @ts-expect-error
typeCastEnum<Enum1>(Enum2.C); // err, 'c' does not fit
// @ts-expect-error
typeCastEnum<Enum1>('c'); // err, 'c' does not fit
// @ts-expect-error
typeCastEnum<Enum1>(Enum2.A as Enum2); // err, some values of Enum2 do not fit
// @ts-expect-error
typeCastEnum<Enum1>(Enum2.Y); // err
// @ts-expect-error
typeCastEnum<Enum1>(99); // err
// @ts-expect-error
typeCastEnum<Enum1>('foo'); // err

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接