我有一个像这样的字符串联合类型:
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;
}
这没关系,该函数确保我总是传递一个对象,其中每个键是字符串union中的一个元素,并且包含每个元素,并返回所有元素的字符串数组。因此,如果字符串union更改,则在编译时对此函数的调用将发生错误,如果还没有更新的话。
然而 问题是 ,常量ALL_SUITS
的类型签名是('hearts' | 'diamonds' | 'spades' | 'clubs')[]
。换句话说,TypeScript认为它是一个包含没有或多个这些值的数组,可能带有重复项,而不是只包含所有值的数组,例如['hearts', 'diamonds', 'spades', 'clubs']
。
我真正喜欢的是我的通用getAllStringUnionValues
函数指定它返回['hearts', 'diamonds', 'spades', 'clubs']
的方法。
如何在尽可能DRY的情况下实现 一般 ?
答案 0 :(得分:32)
在TypeScript 3.4, which should be released in March 2019中,可以告诉编译器将文字元组的类型推断为文字元组,而不是像string[]
那样,使用as const
syntax。这种类型的断言使编译器推断出一个值可能的最窄类型,包括创建所有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
这将消除对任何类型的辅助函数的需要。祝大家好运!
看起来,从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
由于我发布了这个答案,如果你愿意为你的库添加一个函数,我找到了一种推断元组类型的方法。查看tuple.ts中的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: '♣'
}
希望有所帮助。
答案 1 :(得分:2)
TypeScript 3.4附带了更简洁的语法,称为“ const contexts” 。它已经合并到主服务器中,应该很快as seen in this PR可用。
此功能将使通过使用as const
或<const>
关键字创建不可变的(常量)元组类型/数组成为可能。由于无法修改此数组,因此TypeScript可以安全地采用较窄的文字类型['a', 'b']
而不是较宽的('a' | 'b')[]
甚至是string[]
类型,我们可以跳过对{{1 }}功能。
参考您的问题
但是问题是常量ALL_SUITS的类型签名是('hearts'|'diamonds'|'spades'|'clubs')[]。 (...应该是) ['hearts','diamonds','spades','clubs']
使用新语法,我们可以实现以下目标:
tuple()
使用此不可变数组,我们可以轻松创建所需的联合类型:
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']
答案 2 :(得分:0)
使用keyof
,我们可以将并集转换为对象键的数组。可以将其重新应用到数组中。
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;
如果有人向IFullObject
添加新密钥,此方法会增加一些开销,但会出错。
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);