【问题标题】: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

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

【问题讨论】:

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

标签: typescript


【解决方案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]

【讨论】:

【解决方案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
    
    

    【讨论】:

      猜你喜欢
      • 2020-06-15
      • 1970-01-01
      • 2021-09-16
      • 2022-01-05
      • 2020-10-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多