Typescript выводить тип объединения из значений tuple/array

Скажем, у меня есть список const list = ['a', 'b', 'c']

Можно ли извлечь из этого типа объединения значений, который является 'a' | 'b' | 'c' '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

В TypeScript 3.4, который должен быть выпущен в марте 2019 года, можно будет указать компилятору выводить тип кортежа литералов как кортеж литералов, а не как, скажем, string[], используя синтаксис as const, Утверждение этого типа заставляет компилятор выводить наиболее узкий тип, возможный для значения, в том числе делая все доступным только для readonly. Это должно выглядеть так:

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

Это избавит от необходимости использовать вспомогательные функции любого рода. Удачи снова всем!


ОБНОВЛЕНИЕ июль 2018

Похоже, начиная с 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

Поскольку я опубликовал этот ответ, я нашел способ определить типы кортежей, если вы хотите добавить функцию в свою библиотеку. Проверьте функцию tuple() в tuple.ts. Используя его, вы можете написать следующее и не повторять себя:

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

Удачи!


ОРИГИНАЛ июль 2017

Одной из проблем является то, что литерал ['a','b','c'] будет выводиться как string[] типа 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'.

Надеюсь, это поможет.

Ответ 2

Обновление для TypeScript 3.4:

Новый синтаксис, называемый "const contexts", который появится в TypeScript 3.4, позволит создать еще более простое решение, которое не требует вызова функции, как продемонстрировано. Эта функция в настоящее время рассматривается, как видно в этом 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

Это невозможно сделать с помощью массива.

Причина в том, что даже если вы объявляете переменную как const, содержимое массива все равно может измениться, поэтому @jonrsharpe упоминает, что это время выполнения.

Учитывая то, что вы хотите, лучше использовать interface с keyof:

interface X {
    a: string,
    b: string
}

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

Или enum:

enum X { a, b, c }