TypeScript 字符串联合到字符串数组

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/44480644/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 04:37:42  来源:igfitidea点击:

TypeScript String Union to String Array

typescript

提问by CodeAndCats

I have a string union type like so:

我有一个像这样的字符串联合类型:

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

I want a type-safe way to get all the possible values that can be used in this string union. But because interfaces are largely a design-time construct, the best I can do is this:

我想要一种类型安全的方法来获取可以在此字符串联合中使用的所有可能值。但是因为接口在很大程度上是一个设计时构造,所以我能做的最好的是:

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;
}

This works okay, the function ensures I always pass an object where each key is an element in the string union and that every element is included, and returns a string array of all the elements. So if the string union ever changes, the call to this function will error at compile time if not also updated.

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

However the problem isthe type signature for the constant ALL_SUITSis ('hearts' | 'diamonds' | 'spades' | 'clubs')[]. In other words, TypeScript thinks it is an array containing none or more of these values possibly with duplicates, rather than an array containing all the values just once, e.g. ['hearts', 'diamonds', 'spades', 'clubs'].

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

What I'd really like is a way for my generic getAllStringUnionValuesfunction to specify that it returns ['hearts', 'diamonds', 'spades', 'clubs'].

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

How can I achieve this genericallywhile being as DRYas possible?

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

回答by jcalz

The most straightforward way to get what you want is to specify the tuple type explicitly and derive the union from it, instead of trying to force TypeScript to do the reverse, which it doesn't know how to do.

获得你想要的最直接的方法是明确指定元组类型并从中派生联合,而不是试图强制 TypeScript 做相反的事情,它不知道如何做

Update Feb 2019

2019 年 2 月更新

In TypeScript 3.4, which should be released in March 2019it will be possible to tell the compiler to infer the type of a tuple of literals as a tuple of literals, instead of as, say, string[], by using the as constsyntax. This type of assertion causes the compiler to infer the narrowest type possible for a value, including making everything readonly. It should look like this:

应于 2019 年 3 月发布的 TypeScript 3.4 中,可以告诉编译器将文字元组的类型推断为文字元组,而不是string[]使用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

This will obviate the need for a helper function of any kind. Good luck again to all!

这将消除对任何类型的辅助函数的需要。再次祝大家好运!



Update July 2018

2018 年 7 月更新

It looks like, starting with TypeScript 3.0, it will be possible for TypeScript to automatically infer tuple types. Once that is released, the tuple()function you need can be succinctly written as:

看起来,从 TypeScript 3.0 开始,TypeScript 可以自动推断元组类型。一旦它被释放,tuple()你需要的函数可以简洁地写成:

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

And then you can use it like this:

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

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


Update August 2017

2017 年 8 月更新

Since I posted this answer, I found a way to infer tuple types if you're willing to add a function to your library. Check out the function tuple()in tuple.ts.

自从我发布了这个答案,如果您愿意向库中添加一个函数,我就找到了一种推断元组类型的方法。退房的功能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;
}

Using it, you are able to write the following and not repeat yourself:

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

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

What do you think?

你怎么认为?



Original Answer

原答案

The most straightforward way to get what you want is to specify the tuple type explicitly and derive the union from it, instead of trying to force TypeScript to do the reverse, which it doesn't know how to do. For example:

获得你想要的最直接的方法是明确指定元组类型并从中派生联合,而不是试图强制 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

Note that you are still writing out the literals twice, once as types in SuitTupleand once as values in ALL_SUITS; you'll find there's no great way to avoid repeating yourself this way, since TypeScript cannot currently be told to infer tuples, and it will nevergenerate the runtime array from the tuple type.

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

The advantage here is you don't require key enumeration of a dummy object at runtime. You can of course build types with the suits as keys if you still need them:

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

const symbols: {[K in Suit]: string} = {
  hearts: '?', 
  diamonds: '?', 
  spades: '?', 
  clubs: '?'
}

Hope that helps.

希望有帮助。

回答by ggradnig

Update for TypeScript 3.4:

TypeScript 3.4 更新:

There will be a more concise syntax coming with TypeScript 3.4 called "const contexts". It is already merged into master and should be available soon as seen in this PR.

TypeScript 3.4 将提供一种更简洁的语法,称为“const contexts”。它已经合并到 master 中,应该很快就会在这个 PR 中看到

This feature will make it possible to create an immutable (constant) tuple type / array by using the as constor <const>keywords. Because this array can't be modified, TypeScript can safely assume a narrow literal type ['a', 'b']instead of a wider ('a' | 'b')[]or even string[]type and we can skip the call of a tuple()function.

此功能将使使用as constor<const>关键字创建不可变(常量)元组类型/数组成为可能。因为这个数组不能被修改,TypeScript 可以安全地假设一个窄的文字类型['a', 'b']而不是一个更宽的('a' | 'b')[]甚至string[]类型,我们可以跳过tuple()函数的调用。

To refer to your question

参考你的问题

However the problem isthe type signature for the constant ALL_SUITS is ('hearts' | 'diamonds' | 'spades' | 'clubs')[]. (... it should rather be)['hearts', 'diamonds', 'spades', 'clubs']

然而,问题是常量 ALL_SUITS 的类型签名是 ('hearts' | 'diamonds' | 'spades' | 'clubs')[]。 (... 应该是)['hearts', 'diamonds', 'spades', 'clubs']

With the new syntax, we are able to achieve exactly that:

使用新语法,我们能够准确地实现:

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']

With this immutable array, we can easily create the desired union type:

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

type Suits = typeof ALL_SUITS[number]  

回答by Akxe

Method for transforming string union into a non-duplicating array

将字符串联合转换为非重复数组的方法

Using keyofwe can transform union into an array of keys of an object. That can be reapplied into an array.

使用keyof我们可以将 union 转换为对象的键数组。这可以重新应用到数组中。

Playground link

游乐场链接

type Diff<T, U> = T extends U ? never : T;

interface IEdiatblePartOfObject {
    name: string;
}

/**
 * At least one key must be present, 
 * otherwise anything would be assignable to `keys` object.
 */
interface IFullObject extends IEdiatblePartOfObject {
    potato: string;
}

type toRemove = Diff<keyof IFullObject, keyof IEdiatblePartOfObject>;

const keys: { [keys in toRemove]: any } = {
    potato: void 0,
};

const toRemove: toRemove[] = Object.keys(keys) as any;

This method will create some overhead but will error out, if someone adds new keys to IFullObject.

此方法会产生一些开销,但会出错,如果有人将新密钥添加到IFullObject.

Bonus:

奖金:

declare const safeData: IFullObject;
const originalValues: { [keys in toRemove]: IFullObject[toRemove] } = {
    potato: safeData.potato || '',
};

/**
 * This will contain user provided object,
 * while keeping original keys that are not alowed to be modified
 */
Object.assign(unsafeObject, originalValues);