Typescript 字符串并集到字符串数组

Typescript 字符串并集到字符串数组,typescript,Typescript,我有一个字符串联合类型,如下所示: type Suit = 'hearts' | 'diamonds' | 'spades' | 'clubs'; 我需要一种类型安全的方法来获取此字符串联合中可以使用的所有可能值。但由于接口在很大程度上是一种设计时构造,因此我只能做到以下几点: export const ALL_SUITS = getAllStringUnionValues<Suit>({ hearts: 0, diamonds: 0, spades: 0,

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

type Suit = 'hearts' | 'diamonds' | 'spades' | 'clubs';
我需要一种类型安全的方法来获取此字符串联合中可以使用的所有可能值。但由于接口在很大程度上是一种设计时构造,因此我只能做到以下几点:

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;
}
export const ALL\u SUITS=GetAllStringUnionValue({
红心:0,
钻石:0,
黑桃:0,
俱乐部:0
});
导出函数getAllStringUnionValues(值密钥:{[K in TStringUnion]:0}):TStringUnion[]{
const result=Object.getOwnPropertyNames(valuesaskies);
返回任何结果;
}
这可以正常工作,函数确保我始终传递一个对象,其中每个键都是字符串联合中的一个元素,并且每个元素都包含在内,并返回所有元素的字符串数组。因此,如果字符串联合发生变化,那么对该函数的调用将在编译时出错(如果没有更新)

但是问题是常量
所有套装的类型签名是
(“红心”|“钻石”|“黑桃”|“梅花”)[]
。换句话说,TypeScript认为它是一个数组,其中不包含一个或多个可能存在重复的值,而不是一个数组,它只包含一次所有值,例如,
['hearts'、'diamonds'、'spades'、'clubs']

我真正想要的是让我的泛型
getAllStringUnionValues
函数指定它返回
[“红心”、“钻石”、“黑桃”、“梅花”]


如何在尽可能的情况下实现这一点?

最简单的方法是显式指定元组类型并从中派生并集,而不是试图强制TypeScript执行相反的操作,而这正是它所需要的

更新日期:2019年2月 在这种情况下,可以使用。这种类型的断言会使编译器推断出一个值可能的最窄类型,包括使所有内容都
为只读。应该是这样的:

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
const ALL_SUITS = tuple('hearts', 'diamonds', 'spades', 'clubs');
type SuitTuple = typeof ALL_SUITS;
type Suit = SuitTuple[number];  // union type
这样就不需要任何类型的助手函数。祝大家好运


2018年7月更新 看起来,从TypeScript3.0开始,TypeScript3.0就可以运行了。发布后,所需的
tuple()
函数可以简洁地写成:

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

2017年8月更新 因为我发布了这个答案,所以我找到了一种推断元组类型的方法,如果您愿意向库中添加函数的话。查看中的函数
tuple()

你觉得怎么样


原始答案
获取所需内容的最直接的方法是显式指定元组类型并从中派生联合,而不是试图强制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
请注意,您仍然在两次写入文本,一次作为类型写入
suiteple
,一次作为值写入
ALL_suites
;您会发现,没有什么好办法可以避免以这种方式重复您自己,因为TypeScript当前无法被告知,它将从元组类型生成运行时数组

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

const symbols: {[K in Suit]: string} = {
  hearts: '♥', 
  diamonds: '♦', 
  spades: '♠', 
  clubs: '♣'
}
希望对您有所帮助。

TypeScript 3.4的更新: TypeScript 3.4将提供一种更简洁的语法,称为“const context”。它已经合并到master中,应该很快就可以使用

此功能可以使用
as const
关键字创建不可变(常量)元组类型/数组。由于此数组无法修改,TypeScript可以安全地假定一个较窄的文本类型
['a',b']
,而不是较宽的
('a'|'b')[]
甚至
字符串[]
类型,我们可以跳过对
tuple()
函数的调用

参考你的问题

然而,问题是常量ALL|u SUITS的类型签名是('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']
将字符串并集转换为不重复数组的方法 使用
keyof
我们可以将union转换为对象的键数组。可以重新应用到数组中的


哦,推断元组类型很好。这绝对是我见过的最干燥的解决方案。谢谢这也是一种从元组类型(
SuitTuple[number]
)推断联合类型的有趣语法。@ShaunLuttin您可能使用的是3.0之前的TypeScript版本。仔细检查后再联系我。你说的对。我不清楚
SuitTuple[number]
的意思是“在此处放置任何数字”,因此对于未来的读者:在此处放置任何数字,它将为您提供所有条目的联合类型,而不是返回特定条目。为了让未来的程序员不那么困惑(也许?),我使用了
-1
,使它明显与任何实际条目无关。它可以编译,但
Suit
将变成
字符串,而不是文字类型的联合。另外,
SuitTuple
也不会是一个tuple,如果它重要的话。听起来很棒,期待着它!3.3.1已经登陆,但是这个功能还没有出现。你是对的,它看起来要到3.4才会出现。我根据感谢@Akxe编辑了这篇文章。我发现公认的答案是最干燥的解决方案,因此不久前基于它创建了一个小的npm包。如果你感兴趣,可以在这里找到。
const symbols: {[K in Suit]: string} = {
  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']
type Suits = typeof ALL_SUITS[number]  
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;
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);