Typescript wyprowadza typ unii z wartości krotki / tablicy


94

Powiedz, że mam listę const list = ['a', 'b', 'c']

Czy można wyprowadzić z tego typu unii wartości 'a' | 'b' | 'c'?

Chcę tego, ponieważ chcę zdefiniować typ, który zezwala tylko na wartości z tablicy statycznej, a także muszę wyliczyć te wartości w czasie wykonywania, więc używam tablicy.

Przykład, jak można to zaimplementować z indeksowanym obiektem:

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

Zastanawiam się, czy da się to zrobić bez użycia indeksowanej mapy.


Czy chcesz generować typy w locie?
— SwiftsNamesake

Typy istnieją tylko podczas kompilacji, więc nie można ich tworzyć dynamicznie w czasie wykonywania.
— jonrsharpe

To ciekawe pytanie. Jaki jest Twój przypadek użycia?
— unional

Odpowiedzi:


198

AKTUALIZACJA luty 2019 r

W języku TypeScript 3.4, który powinien zostać wydany w marcu 2019 r ., Będzie można poinstruować kompilator, aby wywnioskował typ krotki literałów jako krotkę literałów , zamiast, jak powiedzmy, string[]użyć as constskładni . Ten typ asercji powoduje, że kompilator wywnioskuje najwęższy możliwy typ wartości, w tym tworzenie wszystkiego readonly. To powinno wyglądać tak:

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

Pozwoli to wyeliminować potrzebę jakiejkolwiek funkcji pomocniczej. Powodzenia znowu wszystkim!


UPDATE lipiec 2018

Wygląda na to, że począwszy od TypeScript 3.0, TypeScript będzie mógł automatycznie wywnioskować typy krotek . Po zwolnieniu tuple()funkcję, której potrzebujesz, można zwięźle zapisać jako:

export type Lit = string | number | boolean | undefined | null | void | {};
export const tuple = <T extends Lit[]>(...args: T) => args;

A potem możesz go używać w ten sposób:

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

Mam nadzieję, że to działa dla ludzi!


UPDATE grudzień 2017

Odkąd opublikowałem tę odpowiedź, znalazłem sposób na wywnioskowanie typów krotek, jeśli chcesz dodać funkcję do swojej biblioteki. Sprawdź funkcję tuple()w tuple.ts . Używając go, jesteś w stanie napisać następujące rzeczy i nie powtarzać się:

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

Powodzenia!


ORYGINALNY lipiec 2017

Jednym z problemów jest to, że literał ['a','b','c']zostanie wywnioskowany jako typ string[], więc system typów zapomni o określonych wartościach. Możesz wymusić na systemie typów zapamiętanie każdej wartości jako literału:

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

A może lepiej zinterpretować listę jako typ krotki:

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

Jest to irytujące powtarzanie, ale przynajmniej nie wprowadza obcego obiektu w czasie wykonywania.

Teraz możesz otrzymać swój związek w następujący sposób:

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

Mam nadzieję, że to pomoże.


Jest to doskonałe rozwiązanie problemu, z którym często się spotykam, gdy potrzebuję sprawdzeń czasu wykonania (przy użyciu krotki) i kontroli czasu kompilacji za pomocą unii typów. Czy ktoś wie, czy istnieją próby dodania obsługi niejawnego pisania krotek do języka?
— Jørgen Tvedt

Wypróbowałem twoje rozwiązanie, ale NIE działacont xs = ['a','b','c']; const list = tuple(...xs);
— Miguel Carvajal

1
To nie ma do pracy z tym, ponieważ do czasu można zrobić const xs = ['a','b','c']kompilator już rozszerzyły xssię string[]i całkowicie zapomnieli o wartościach określonych. Nie mogę nic na to poradzić, przynajmniej od wersji TS3.2 (może być jednak przyszła as constnotacja, która działa). W każdym razie myślę, że odpowiedź w obecnym stanie jest nadal tak poprawna, jak mogę to zrobić (wspominam tam, że ['a','b','c']jest to wywnioskowane jako string[]), więc nie jestem pewien, czego więcej potrzebujesz.
— jcalz

3
czy ktoś może wyjaśnić, co [number]robi w list[number]?
— Orelus

3
To jest analizowane jako (typeof list)[number]... nie typeof (list[number]). Typ T[K]jest typem wyszukiwania, który pobiera typ właściwości, Tktórej klucz jest K. W programie (typeof list)[number]otrzymujesz typy właściwości, (typeof list)których klucze są number. Tablice takie jak typeof listmają numeryczne sygnatury indeksowe , więc ich numberklucz daje sumę wszystkich właściwości indeksowanych numerycznie.
— jcalz

15

Aktualizacja dla języka TypeScript 3.4:

Nowa składnia o nazwie „const contexts”, która pojawi się w TypeScript 3.4, pozwoli na jeszcze prostsze rozwiązanie, które nie wymaga wywołania funkcji, jak pokazano. Ta funkcja jest obecnie sprawdzana, jak widać w tym żądaniu .

Krótko mówiąc, składnia ta pozwala na tworzenie niezmiennych tablic, które mają wąski typ (tj. Typ ['a', 'b', 'c']zamiast ('a' | 'b' | 'c')[]lub string[]). W ten sposób możemy tworzyć typy unii z literałów tak łatwo, jak pokazano poniżej:

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

W alternatywnej składni:

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

Cześć, właśnie znalazłem odpowiedź po opublikowaniu tego pytania. Pozwolę ci zarobić trochę karmy, jeśli chcesz odpowiedzieć;) stackoverflow.com/questions/56113411/…
— Sebastien Lorber

2

Nie można tego zrobić w przypadku Array.

Powodem jest to, że nawet jeśli zadeklarujesz zmienną jako stałą, zawartość tablicy może się zmienić, dlatego @jonrsharpe wspomina, że ​​jest to środowisko uruchomieniowe.

Biorąc pod uwagę to, czego chcesz, lepiej użyć interfacez keyof:

interface X {
    a: string,
    b: string
}

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

Lub enum:

enum X { a, b, c }

Czy istnieje sposób na wykluczenie niektórych pól? Na przykład, jeśli chcę tylko apole ...
— neomib

Możesz użyć Pick<T, U>do tego.
— unional
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.