TypeScript String Union to String Array

Cod*_*ats 32 typescript

我有一个字符串联合类型,如下所示:

type Suit = 'hearts' | 'diamonds' | 'spades' | 'clubs';
Run Code Online (Sandbox Code Playgroud)

我想要一种类型安全的方法来获取可以在此字符串联合中使用的所有可能值.但由于接口主要是设计时构造,我能做的最好的就是:

export const ALL_SUITS = getAllStringUnionValues<Suit>({
    hearts: 0,
    diamonds: 0,
    spades: 0,
    clubs: 0
});

export function getAllStringUnionValues<TStringUnion extends string>(valuesAsKeys: { [K in TStringUnion]: 0 }): TStringUnion[] {
    const result = Object.getOwnPropertyNames(valuesAsKeys);
    return result as any;
}
Run Code Online (Sandbox Code Playgroud)

这没关系,该函数确保我总是传递一个对象,其中每个键是字符串union中的一个元素,并且每个元素都包含在内,并返回所有元素的字符串数组.因此,如果字符串union更改,则在编译时对此函数的调用将发生错误,如果还未更新的话.

问题是常量的类型签名ALL_SUITS('hearts' | 'diamonds' | 'spades' | 'clubs')[].换句话说,TypeScript认为它是一个包含没有或多个这些值的数组,可能带有重复项,而不是只包含所有值的数组,例如['hearts', 'diamonds', 'spades', 'clubs'].

我真正喜欢的是我的泛型getAllStringUnionValues函数指定它返回的方法['hearts', 'diamonds', 'spades', 'clubs'].

我怎样才能做到这一点一般,同时为越好?

jca*_*alz 48

2019年2月更新

应该在2019年3月发布的TypeScript 3.4中,可以通过使用语法告诉编译器将文字元组的类型推断为文字元组,而不是说.这种类型的断言使编译器推断出一个值可能的最窄类型,包括制作所有内容.它应该如下所示:string[]as constreadonly

const ALL_SUITS = ['hearts', 'diamonds', 'spades', 'clubs'] as const; // TS 3.4
type SuitTuple = typeof ALL_SUITS; // readonly ['hearts', 'diamonds', 'spades', 'clubs']
type Suit = SuitTuple[number];  // union type
Run Code Online (Sandbox Code Playgroud)

这将消除对任何类型的辅助功能的需要.祝大家好运!


2018年7月更新

看起来,从TypeScript 3.0开始,TypeScript可以自动推断元组类型.一旦发布,tuple()您需要的功能可以简洁地写为:

export type Lit = string | number | boolean | undefined | null | void | {};
export const tuple = <T extends Lit[]>(...args: T) => args;
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样使用它:

const ALL_SUITS = tuple('hearts', 'diamonds', 'spades', 'clubs');
type SuitTuple = typeof ALL_SUITS;
type Suit = SuitTuple[number];  // union type
Run Code Online (Sandbox Code Playgroud)

2017年8月更新

自从我发布这个答案后,如果你愿意为你的库添加一个函数,我找到了一种推断元组类型的方法.退房的功能tuple()tuple.ts.

export type Lit = string | number | boolean | undefined | null | void | {};

// infers a tuple type for up to twelve values (add more here if you need them)
export function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit, I extends Lit, J extends Lit, K extends Lit, L extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L): [A, B, C, D, E, F, G, H, I, J, K, L];
export function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit, I extends Lit, J extends Lit, K extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K): [A, B, C, D, E, F, G, H, I, J, K];
export function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit, I extends Lit, J extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J): [A, B, C, D, E, F, G, H, I, J];
export function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit, I extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I): [A, B, C, D, E, F, G, H, I];
export function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H): [A, B, C, D, E, F, G, H];
export function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G): [A, B, C, D, E, F, G];
export function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F): [A, B, C, D, E, F];
export function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit>(a: A, b: B, c: C, d: D, e: E): [A, B, C, D, E];
export function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit>(a: A, b: B, c: C, d: D): [A, B, C, D];
export function tuple<A extends Lit, B extends Lit, C extends Lit>(a: A, b: B, c: C): [A, B, C];
export function tuple<A extends Lit, B extends Lit>(a: A, b: B): [A, B];
export function tuple<A extends Lit>(a: A): [A];
export function tuple(...args: any[]): any[] {
  return args;
}
Run Code Online (Sandbox Code Playgroud)

使用它,您可以编写以下内容而不是重复:

const ALL_SUITS = tuple('hearts', 'diamonds', 'spades', 'clubs');
type SuitTuple = typeof ALL_SUITS;
type Suit = SuitTuple[number];  // union type
Run Code Online (Sandbox Code Playgroud)

你怎么看?


原始答案

获得所需内容的最简单方法是明确指定元组类型并从中派生联合,而不是试图强制TypeScript执行反向操作,而不知道该怎么做.例如:

type SuitTuple = ['hearts', 'diamonds', 'spades', 'clubs'];
const ALL_SUITS: SuitTuple = ['hearts', 'diamonds', 'spades', 'clubs']; // extra/missing would warn you
type Suit = SuitTuple[number];  // union type
Run Code Online (Sandbox Code Playgroud)

请注意,您仍然会两次写出文字,一次作为输入SuitTuple,一次作为值ALL_SUITS; 你会发现没有很好的方法可以避免这种方式重复自己,因为当前无法告诉TypeScript 推断元组,它永远不会从元组类型生成运行时数组.

这里的优点是您不需要在运行时对虚拟对象进行密钥枚举.如果你仍然需要它们,你当然可以用西装作为键来构建类型:

const symbols: {[K in Suit]: string} = {
  hearts: '?', 
  diamonds: '?', 
  spades: '?', 
  clubs: '?'
}
Run Code Online (Sandbox Code Playgroud)

希望有所帮助.

  • 我不清楚 `SuitTuple[number]` 的意思是“在此处放置任何数字”,因此对于未来的读者:在此处放置任何数字,它将为您提供所有条目的联合类型,而不是返回该特定条目。为了让未来的程序员不那么困惑(也许?),我使用了“-1”,以使其显然与任何实际条目无关。 (3认同)
  • 我还没有深入研究它,但它看起来像 `const ALL_SUITS = ['hearts', 'diamonds', 'spades', 'clubs'] as const; type Suit = typeof ALL_SUITS[number];` 有效。如果是这样,则无需创建“元组”类型。 (3认同)
  • @UniversE 我已经编辑得更明确地说联合到元组实际上是不可能的(或者至少不建议,带有一个在 TS2.8 发布后变得可能的脆弱方法的链接)以及原因。解决方法是存在的,因为实际的目标是尽可能以 DRY 的方式拥有并集和元组,而并集到元组可能只是 [XY 问题](https://en.wikipedia) 中的 Y .org/wiki/XY_problem)。如果答案停留在“抱歉,你做不到”,则无助于实现目标。我希望编辑后的介绍部分能够解决您的担忧,或者至少承认它们。 (3认同)
  • 哦,推断元组类型做得很好。这绝对是我现在见过的最干燥的解决方案。谢谢!这也是从元组类​​型(`SuitTuple[number]`)推断联合类型的有趣语法。 (2认同)
  • 它可以编译,但是`Suit`会变成`string`而不是文字类型的并集。同样,如果重要的话,`SuitTuple`也不会是一个元组。 (2认同)
  • 这是一个经典的“接受”答案。一个根本没有回答问题的帖子。问题是如何将联合类型转换为字符串数组。答案是:看,这里将数组转换为联合类型(这正是_另一种方式_)。因此,只需自己编写数组,然后从中获取联合类型即可。精彩的。虽然这对于 OP 来说可能是可能的,但这并不能回答最初的问题并且仍然获得 150 票赞成?也许正确的答案是:打字稿不支持这一点。但是,*this* 应该是接受的答案,解决方法是评论。 (2认同)

f v*_*f v 14

轻松而正确地在心里。

字符串联合到字符串数组 - 正确的决定!

type ValueOf<T> = T[keyof T];

type NonEmptyArray<T> = [T, ...T[]]

type MustInclude<T, U extends T[]> = [T] extends [ValueOf<U>] ? U : never;

function stringUnionToArray<T>() {
  return <U extends NonEmptyArray<T>>(...elements: MustInclude<T, U>) => elements;
}


/* USAGE */
type Variants = "error" | "success" | "info";

// This is what You want!! :)
let stringArray = stringUnionToArray<Variants>()("error", "success", "info");
Run Code Online (Sandbox Code Playgroud)

  • 等等,您必须将所有联合类型成员指定为“stringUnionToArray”的参数?但这正是我们想要避免的!如果需要的话,我可以用这些值定义数组,而不是通过所有额外的代码...... (47认同)
  • 是的,但它经过类型检查。这意味着,如果将新值添加到联合中,您将收到编译时错误:提醒更新值。它不是自动的,但它是对值的硬编码的严格改进。 (2认同)
  • @negamartin 普通数组也可以是类型安全的: `let stringArray: Variants[] = ['error', 'success', 'info'];` (2认同)

ggr*_*nig 5

TypeScript 3.4的更新:

TypeScript 3.4附带了更简洁的语法,称为“ const contexts”。它已经合并到master中,因此在PR中应该可以立即使用。

此功能将使通过使用as constor <const>关键字创建不可变(恒定)元组类型/数组成为可能。由于无法修改此数组,因此TypeScript可以安全地采用较窄的文字类型,['a', 'b']而不是较宽的类型,('a' | 'b')[]甚至string[]可以忽略,而我们可以跳过对tuple()函数的调用。

提及你的问题

但是,问题在于常量ALL_SUITS的类型签名为('hearts'|'diamonds'|'spades'|'clubs')[]。 (...宁可这样) ['hearts','diamonds','spades','clubs']

使用新的语法,我们可以准确地实现以下目标:

const ALL_SUITS = <const> ['hearts', 'diamonds', 'spades', 'clubs'];  
// or 
const ALL_SUITS = ['hearts', 'diamonds', 'spades', 'clubs'] as const;

// type of ALL_SUITS is infererd to ['hearts', 'diamonds', 'spades', 'clubs']
Run Code Online (Sandbox Code Playgroud)

有了这个不可变的数组,我们可以轻松地创建所需的联合类型:

type Suits = typeof ALL_SUITS[number]  
Run Code Online (Sandbox Code Playgroud)