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

type Suit = 'hearts' | 'diamonds' | 'spades' | 'clubs';

我想要一种类型安全的方法来获取可以在此字符串联合中使用的所有可能的值。但是由于接口(interface)在很大程度上是设计时构造,所以我能做的就是:
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;
}

这项工作正常,该函数确保我始终传递一个对象,其中每个键是字符串联合中的一个元素,并且包括每个元素,并返回所有元素的字符串数组。因此,如果字符串并集发生更改,则该函数的调用将在编译时发生错误(如果未更新)。

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

我真正想要的是一种通用的getAllStringUnionValues函数指定它返回['hearts', 'diamonds', 'spades', 'clubs']的方式。

我如何在尽可能地成为DRY的同时实现此(通常为)?

最佳答案

获得所需内容的最直接方法是显式指定元组类型并从中派生联合,而不是尝试强制TypeScript执行相反操作,即doesn't know how to do

更新2019年2月

TypeScript 3.4, which should be released in March 2019中,可以使用 string[] syntax告诉编译器将文字的元组的类型推断为文字的元组,而不是as const。这种类型的断言使编译器推断出一个值可能的最窄类型,包括使所有内容变为readonly。它看起来应该像这样:

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

这将消除对任何辅助功能的需要。再次祝大家好运!

2018年7月更新

从TypeScript 3.0开始,TypeScript可能会变成automatically infer tuple types。一旦发布,您所需的tuple()函数可以简洁地编写为:
export type Lit = string | number | boolean | undefined | null | void | {};
export const tuple = <T extends Lit[]>(...args: T) => args;

然后您可以像这样使用它:
const ALL_SUITS = tuple('hearts', 'diamonds', 'spades', 'clubs');
type SuitTuple = typeof ALL_SUITS;
type Suit = SuitTuple[number];  // union type

2017年8月更新

自从我发布了这个答案以来,如果您愿意向库中添加函数,我就找到了一种推断元组类型的方法。在tuple.ts中 checkout 函数tuple()
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;
}

使用它,您可以编写以下内容而不再重复:
const ALL_SUITS = tuple('hearts', 'diamonds', 'spades', 'clubs');
type SuitTuple = typeof ALL_SUITS;
type Suit = SuitTuple[number];  // union type

你怎么看?

原始答案

获得所需内容的最直接方法是显式指定元组类型并从中派生联合,而不是尝试强制TypeScript执行相反操作,即doesn't know how to do。例如:
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

注意,您仍在写两次文字,一次是SuitTuple中的类型,一次是ALL_SUITS中的值;您会发现,没有什么好方法可以避免重复这种方式,因为当前无法将TypeScript告知infer tuples,并且never将从元组类型生成运行时数组。

这样做的好处是您不需要在运行时对虚拟对象进行键枚举。当然,如果仍然需要,可以使用西装作为键来构建类型:
const symbols: {[K in Suit]: string} = {
  hearts: '♥',
  diamonds: '♦',
  spades: '♠',
  clubs: '♣'
}

希望能有所帮助。

关于typescript - 字符串联合到字符串数组,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44480644/

10-09 18:00
查看更多