假设我有一个数组:

const list = ['a', 'b', 'c']

是否有可能从这个值联合类型中派生出'a' | 'b' | 'c'?

我想这样做是因为我想定义类型,它只允许来自静态数组的值,还需要在运行时枚举这些值,所以我使用数组。

示例如何使用索引对象实现:

const indexed = {a: null, b: null, c: null}
const list = Object.keys(index)
type NeededUnionType = keyof typeof indexed

是否可以在不使用索引映射的情况下做到这一点?


使用Array不可能做到这一点。

原因是,即使将变量声明为const,数组的内容仍然可以更改,因此@jonrsharpe提到这是运行时。

给定你想要的,使用带有keyof的interface可能会更好:

interface X {
    a: string,
    b: string
}

type Y = keyof X  // Y: 'a' | 'b'

或枚举:

enum X { a, b, c }

2019年2月更新

在将于2019年3月发布的TypeScript 3.4中,通过使用as const语法,可以告诉编译器将文字元组的类型推断为文字元组,而不是作为string[]。这种类型的断言导致编译器推断值的最窄类型,包括使所有内容都为只读。它应该是这样的:

const list = ['a', 'b', 'c'] as const; // TS3.4 syntax
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c';

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


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;

然后你可以这样使用它:

const list = tuple('a','b','c');  // type is ['a','b','c']
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c'

希望这对人们有用!


2017年12月更新

自从我发布了这个答案,我发现了一种推断元组类型的方法,如果你愿意向你的库中添加一个函数。在tuple.ts中检出函数tuple()。使用它,你可以写出以下内容,而不必重复:

const list = tuple('a','b','c');  // type is ['a','b','c']
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c'

好运!


2017年7月

一个问题是字面量['a','b','c']将被推断为类型字符串[],因此类型系统将忘记具体的值。你可以强制类型系统将每个值都记为字面值字符串:

const list = ['a' as 'a','b' as 'b','c' as 'c']; // infers as ('a'|'b'|'c')[]

或者,更好的是,将列表解释为元组类型:

const list: ['a','b','c'] = ['a','b','c']; // tuple

这是令人讨厌的重复,但至少它不会在运行时引入无关的对象。

现在你可以像这样得到你的工会:

type NeededUnionType = typeof list[number];  // 'a'|'b'|'c'.

TypeScript 3.4的更新:

在TypeScript 3.4中会出现一种名为“const contexts”的新语法,它将提供一种更简单的解决方案,不需要像演示的那样调用函数。该功能目前正在审查中,如此PR所示。

简而言之,这种语法允许创建具有窄类型的不可变数组(即类型['a', 'b', 'c']而不是('a' | 'b' | 'c')[]或字符串[])。这样,我们就可以简单地从字面量创建联合类型,如下所示:

const MY_VALUES = <const> ['a', 'b', 'c']
type MyType = typeof MY_VALUES[number]

换句话说:

const MY_VALUES = ['a', 'b', 'c'] as const
type MyType = typeof MY_VALUES[number]

如果使用一个对象来存储“常量”,这是一种实现相同思想的方法:

(注意'as const'将keyOne和keyTwo的类型从字符串改为文字。)

const configObj = {
  keyOne: 'literalTypeValueOne' as const,
  keyTwo: 'literalTypeValueTwo' as const,
};

const typeValues = [configObj.keyOne, configObj.keyTwo] as const;
type MyType = typeof typeValues[number];

我假设你现在生活在2019年3月之后的TypeScript上。(2021年11月)

我只是用一个可导出的效用函数扩展了顶部的答案:

// Your handmade utils' library file
export type UnionOfArrayElements<ARR_T extends Readonly<unknown[]>> = ARR_T[number];
// Usage
const a = ["hi", "bye", 3, false] as const;
type ta = UnionOfArrayElements<typeof a>; // false | "hi" | "bye" | 3

const b = [4, 5, 6];
type tb = UnionOfArrayElements<typeof b>; // number