打字稿从元组/数组值派生联合类型

Posted

技术标签:

【中文标题】打字稿从元组/数组值派生联合类型【英文标题】:Typescript derive union type from tuple/array values 【发布时间】:2017-12-28 07:58:14 【问题描述】:

说我有清单 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

我想知道是否可以不使用索引地图来做到这一点。

【问题讨论】:

所以本质上,您想动态生成类型? 类型只在编译时存在,不能在运行时动态创建。 这是一个有趣的问题。你的用例是什么? 【参考方案1】:

2019 年 2 月更新

在TypeScript 3.4, which should be released in March 2019 中,可以通过使用as const syntax。这种类型的断言导致编译器推断出一个值的最窄类型,包括将所有内容设为readonly。它应该是这样的:

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

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


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 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'] 将被推断为类型string[],因此类型系统会忘记具体的值。您可以强制类型系统将每个值记住为文字字符串:

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

希望对您有所帮助。

【讨论】:

当我需要运行时检查(使用元组)和使用类型联合进行编译时检查时,这是我经常遇到的问题的绝佳解决方案。任何人都知道是否有努力为语言添加对隐式元组类型的支持? 试过你的解决方案,但它不适用于cont xs = ['a','b','c']; const list = tuple(...xs); 它不应该与它一起工作,因为当您执行 const xs = ['a','b','c'] 时,编译器已经将 xs 扩大到 string[] 并且完全忘记了具体值。至少从 TS3.2 开始,我无法帮助这种行为(尽管未来可能会有 as const 表示法有效)。无论如何,我认为目前的答案仍然尽可能正确(我在那里提到['a','b','c']被推断为string[])所以我不确定你还需要什么。 有人能解释一下[number]list[number] 中的作用吗? 它被解析为(typeof list)[number]...而不是typeof (list[number])。类型T[K] 是一个lookup type,它获取T 的属性类型,其键为K。在(typeof list)[number] 中,您将获得(typeof list) 的属性类型,其键为number。像typeof list 这样的数组有numeric index signatures,所以它们的number 键产生所有数字索引属性的联合。【参考方案2】:

TypeScript 3.4 更新:

一种名为 "const contexts" 的新语法将出现在 TypeScript 3.4 中,这将允许一个更简单的解决方案,不需要如演示的函数调用。此功能目前正在审核as seen in this PR。

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

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]

【讨论】:

您好,刚刚在发布此问题后找到了您的答案。如果你想回答,我会让你赚点业力;)***.com/questions/56113411/…【参考方案3】:

Array 无法做到这一点。

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

根据您的需要,最好将interfacekeyof 一起使用:

interface X 
    a: string,
    b: string


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

enum:

enum X  a, b, c 

【讨论】:

有没有办法排除某些字段?例如,如果我只想要 a 字段.. 您可以使用Pick&lt;T, U&gt;【参考方案4】:

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

(注意 '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];

【讨论】:

【参考方案5】:

我假设您现在在 2019 年 3 月之后使用 TypeScript。 (现在是 2021 年 11 月)

我只是用一个可导出的实用函数扩展the top answer:

// 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

【讨论】:

以上是关于打字稿从元组/数组值派生联合类型的主要内容,如果未能解决你的问题,请参考以下文章

“联合类型值到字符串的映射”的打字稿类型?

在类型中使用元组而不是联合数组

打字稿:你如何用布尔或回调函数定义联合类型?

扩展排他联合的打字稿通用参数

打字稿从父ID中获取所有子孙ID的数组

无法让嵌套类型保护与打字稿中的联合类型一起使用