Typoskript leitet den Vereinigungstyp aus Tupel- / Array-Werten ab


94

Angenommen, ich habe eine Liste const list = ['a', 'b', 'c']

Ist es möglich, von diesem Wertvereinigungstyp abzuleiten 'a' | 'b' | 'c'?

Ich möchte dies, weil ich einen Typ definieren möchte, der nur Werte aus dem statischen Array zulässt, und diese Werte auch zur Laufzeit auflisten muss, also verwende ich das Array.

Beispiel, wie es mit einem indizierten Objekt implementiert werden kann:

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

Ich frage mich, ob dies ohne Verwendung einer indizierten Karte möglich ist.


Sie möchten also im Wesentlichen Typen im laufenden Betrieb generieren?
SwiftsNamesake

Die Typen sind nur beim Kompilieren vorhanden, sodass Sie sie zur Laufzeit nicht dynamisch erstellen können.
Jonrsharpe

Dies ist eine interessante Frage. Was ist Ihr Anwendungsfall?
unional

Antworten:


199

UPDATE Februar 2019

In TypeScript 3.4, das im März 2019 veröffentlicht werden soll, kann der Compiler angewiesen werden, den Typ eines Tupels von Literalen als Tupel von Literalen abzuleiten , anstatt beispielsweise string[]die as constSyntax zu verwenden . Diese Art der Behauptung führt dazu, dass der Compiler den engstmöglichen Typ für einen Wert ableitet, einschließlich der Erstellung aller Elemente readonly. Es sollte so aussehen:

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

Dadurch entfällt die Notwendigkeit einer Hilfsfunktion jeglicher Art. Nochmals viel Glück an alle!


UPDATE Juli 2018

Es sieht so aus, als ob TypeScript ab TypeScript 3.0 automatisch auf Tupeltypen schließen kann . Nach der Freigabe kann die von tuple()Ihnen benötigte Funktion kurz und bündig geschrieben werden als:

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

Und dann können Sie es so verwenden:

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

Hoffe das funktioniert für Menschen!


UPDATE Dezember 2017

Seit ich diese Antwort gepostet habe, habe ich einen Weg gefunden, Tupeltypen abzuleiten, wenn Sie bereit sind, Ihrer Bibliothek eine Funktion hinzuzufügen. Überprüfen Sie die Funktion tuple()in tuple.ts . Mit ihm können Sie Folgendes schreiben und sich nicht wiederholen:

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

Viel Glück!


ORIGINAL Juli 2017

Ein Problem ist, dass das Literal ['a','b','c']als Typ abgeleitet wird string[], sodass das Typsystem die spezifischen Werte vergisst. Sie können das Typsystem zwingen, sich jeden Wert als Literalzeichenfolge zu merken:

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

Oder interpretieren Sie die Liste besser als Tupeltyp:

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

Dies ist eine ärgerliche Wiederholung, führt aber zur Laufzeit zumindest kein fremdes Objekt ein.

Jetzt können Sie Ihre Gewerkschaft so bekommen:

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

Hoffentlich hilft das.


Dies ist eine hervorragende Lösung für ein Problem, auf das ich häufig stoße, wenn ich Laufzeitprüfungen (mit dem Tupel) und Kompilierungszeitprüfungen mit einer Typunion benötige. Weiß jemand, ob es Anstrengungen gibt, der Sprache Unterstützung für implizite Tupeltipping hinzuzufügen?
Jørgen Tvedt

Versuchte Ihre Lösung, aber es funktioniert nicht mitcont xs = ['a','b','c']; const list = tuple(...xs);
Miguel Carvajal

1
Es ist nicht damit zu arbeiten soll, da durch die Zeit , die Sie tun const xs = ['a','b','c']der Compiler bereits erweitert xsum string[]und vollständig über die spezifischen Werte vergessen. Ich kann dieses Verhalten zumindest ab TS3.2 nicht ändern (es könnte jedoch eine zukünftige as constNotation geben, die funktioniert). Wie auch immer, ich denke, die Antwort ist immer noch so richtig, wie ich sie machen kann (ich erwähne dort, ['a','b','c']was als abgeleitet wird string[]), also bin ich mir nicht sicher, was Sie mehr brauchen.
Jcalz

3
kann jemand erklären was in [number]tut list[number]?
Orelus

3
Es wird analysiert als (typeof list)[number]... nicht typeof (list[number]). Der Typ T[K]ist ein Nachschlagetyp, der den Typ der Eigenschaft abruft, Tderen Schlüssel ist K. In erhalten (typeof list)[number]Sie die Arten der Eigenschaften, (typeof list)deren Schlüssel sind number. Arrays wie typeof listhaben numerische Indexsignaturen , sodass ihr numberSchlüssel die Vereinigung aller numerisch indizierten Eigenschaften ergibt.
Jcalz

15

Update für TypeScript 3.4:

Eine neue Syntax namens "const context" , die in TypeScript 3.4 verfügbar sein wird, ermöglicht eine noch einfachere Lösung, für die kein Funktionsaufruf erforderlich ist, wie gezeigt. Diese Funktion wird derzeit in dieser PR überprüft .

Kurz gesagt, diese Syntax ermöglicht die Erstellung unveränderlicher Arrays mit einem engen Typ (dh dem Typ ['a', 'b', 'c']anstelle von ('a' | 'b' | 'c')[]oder string[]). Auf diese Weise können wir Unionstypen aus Literalen so einfach wie unten gezeigt erstellen:

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

In alternativer Syntax:

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

Hallo, habe gerade deine Antwort gefunden, nachdem ich diese Frage gestellt habe. Ich lasse dich etwas Karma verdienen, wenn du antworten willst;) stackoverflow.com/questions/56113411/…
Sebastien Lorber

2

Mit Array ist dies nicht möglich.

Der Grund dafür ist, dass sich der Inhalt eines Arrays auch dann ändern kann, wenn Sie die Variable als const deklarieren. @Jonrsharpe erwähnt daher, dass dies Laufzeit ist.

Nach dem , was Sie wollen, kann es besser sein zu verwenden , interfacemit keyof:

interface X {
    a: string,
    b: string
}

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

Oder enum:

enum X { a, b, c }

Gibt es eine Möglichkeit, bestimmte Felder auszuschließen? Zum Beispiel, wenn ich nur das aFeld will ..
Neomib

Sie können dafür verwenden Pick<T, U>.
unional
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.