Typinferenz mit TypeScript unter Verwendung des Konstrukts as const und des Schlüsselworts infer

Mit TypeScript können Sie viele Aufgaben automatisieren, die Entwickler ohne diese Sprache unabhängig lösen müssen. Bei der Arbeit mit TypeScript müssen jedoch keine Typanmerkungen ständig verwendet werden. Tatsache ist, dass der Compiler eine hervorragende Arbeit bei der Typinferenz leistet, die auf dem Kontext der Codeausführung basiert. Der Artikel, dessen Übersetzung wir heute veröffentlichen, widmet sich ziemlich komplizierten Fällen von Typinferenz, in denen das Schlüsselwort inferund die Konstruktion verwendet werden as const.



Grundlagen der Typinferenz


Schauen Sie sich zunächst das einfachste Beispiel für eine Typinferenz an.

let variable;

Eine auf diese Weise deklarierte Variable ist vom Typ any. Wir haben dem Compiler keine Hinweise gegeben, wie wir ihn verwenden werden.

let variable = 'Hello!';

Hier haben wir eine Variable deklariert und sofort einen Wert in sie geschrieben. TypeScript kann nun erraten, dass diese Variable vom Typ ist string, sodass wir jetzt eine vollkommen akzeptable typisierte Variable haben.

Ein ähnlicher Ansatz gilt für Funktionen:

function getRandomInteger(max: number) {
  return Math.floor(Math.random() * max);
}

In diesem Code geben wir nicht an, dass die Funktion getRandomIntegereine Zahl zurückgibt. Aber der TypeScript-Compiler weiß das sehr gut.

Typinferenz in Generika


Die obigen Konzepte beziehen sich auf universelle Typen (Generika). Wenn Sie mehr über Generika erfahren möchten, schauen Sie sich dieses und dieses Material an.

Beim Erstellen generischer Typen können Sie viele nützliche Dinge tun. Die Typinferenz erleichtert das Arbeiten mit universellen Typen und vereinfacht sie.

function getProperty<ObjectType, KeyType extends keyof ObjectType>(
  object: ObjectType, key: KeyType
) {
  return object[key];
}

Bei Verwendung der obigen generischen Funktion müssen die Typen nicht explizit angegeben werden.

const dog = {
  name: 'Fluffy'
};
getProperty(dog, 'name');

Diese Technik ist unter anderem sehr nützlich bei der Erstellung universeller React-Komponenten. Hier ist das Material dazu.

Verwenden Sie das Schlüsselwort infer


Eine der fortschrittlichsten TypeScript-Funktionen, die bei der Typinferenz in den Sinn kommen, ist das Schlüsselwort infer.

Betrachten Sie ein Beispiel. Erstellen Sie die folgende Funktion:

function call<ReturnType>(
  functionToCall: (...args: any[]) => ReturnType, ...args: any[]
): ReturnType {
  return functionToCall(...args);
}

Rufen Sie mit Hilfe dieser Funktion eine andere Funktion auf und schreiben Sie, was sie zurückgibt, in eine Konstante:

const randomNumber = call(getRandomInteger, 100);

Mit dem vorherigen Ausdruck können wir ermitteln, welche Funktion getRandomIntegerdie empfangene Eingabe als Obergrenze der an sie zurückgegebenen zufälligen Ganzzahl 100 zurückgegeben hat. Richtig, es gibt ein kleines Problem. Es liegt in der Tatsache, dass uns nichts daran hindert, die Arten von Funktionsargumenten zu ignorieren getRandomInteger.

const randomNumber = call(getRandomInteger, '100'); //   

Da TypeScript Spread- und Rest-Parameter in Funktionen höherer Ordnung unterstützt, können wir dieses Problem folgendermaßen lösen:

function call<ArgumentsType extends any[], ReturnType>(
  functionToCall: (...args: ArgumentsType) => ReturnType, ...args: ArgumentsType
): ReturnType {
  return functionToCall(...args);
}

Jetzt haben wir darauf hingewiesen, dass die Funktion callein Array von Argumenten in beliebiger Form verarbeiten kann und dass die Argumente den Erwartungen der an sie übergebenen Funktion entsprechen müssen.

Versuchen wir nun erneut, einen falschen Funktionsaufruf durchzuführen:

const randomNumber = call(getRandomInteger, '100');

Dies führt zu einer Fehlermeldung:

Argument of type ‘”100″‘ is not assignable to parameter of type ‘number’.

In der Tat haben wir gemäß den obigen Schritten einfach ein Tupel erstellt. Tupel in TypeScript sind Arrays mit fester Länge, deren Werttypen bekannt sind, aber nicht identisch sein müssen.

type Option = [string, boolean];
const option: Option = ['lowercase', true];

Keyword-Funktionen schließen


Stellen wir uns nun vor, dass unser Ziel nicht darin besteht, die Rückgabe der Funktion zu erhalten, sondern nur Informationen über die Art der an sie zurückgegebenen Daten.

type FunctionReturnType<FunctionType extends (...args: any) => ?> = ?;

Der oben genannte Typ ist noch nicht einsatzbereit. Wir müssen das Problem lösen, wie der Rückgabewert ermittelt werden kann. Hier können Sie alles manuell beschreiben, aber dies widerspricht unserem Ziel.

type FunctionReturnType<ReturnType, FunctionType extends (...args: any) => ReturnType> = ReturnType;
FunctionReturnType<number, typeof getRandomInteger>;

Anstatt dies alleine zu tun, können wir TypeScript bitten, den Rückgabetyp auszugeben. Das Schlüsselwort inferkann nur in bedingten Typen verwendet werden. Deshalb kann unser Code manchmal etwas unordentlich sein.

type FunctionReturnType<FunctionType extends (args: any) => any> = FunctionType extends (...args: any) => infer ReturnType ? ReturnType : any;

Folgendes passiert in diesem Code:

  • Hier heißt es zu FunctionTypeexpandieren (args: any) => any.
  • Wir weisen darauf hin, dass FunctionReturnTypedies ein bedingter Typ ist.
  • Wir prüfen, ob es sich ausdehnt FunctionType (...args: any) => infer ReturnType.

Nachdem wir dies alles getan haben, können wir den Rückgabetyp jeder Funktion extrahieren.

FunctionReturnType<typeof getRandomInteger>; // number

Das Obige ist eine so häufige Aufgabe, dass TypeScript über ein integriertes Dienstprogramm ReturnType verfügt , mit dem dieses Problem gelöst werden soll.

Konstruiere als const


Ein weiteres Problem Typinferenz Zusammenhang ist der Unterschied zwischen den Schlüsselwörtern constund letdie verwendet werden , wenn Konstanten und Variablen zu deklarieren.

let fruit = 'Banana';
const carrot = 'Carrot';

Variable fruit- hat einen Typ string. Dies bedeutet, dass jeder Zeichenfolgenwert darin gespeichert werden kann.

Und eine Konstante carrotist ein String-Literal. Es kann als Beispiel für einen Subtyp betrachtet werden string. Die folgende Beschreibung von Zeichenfolgenliteralen wird in dieser PR gegeben : "Das Typzeichenfolgenliteral ist ein Typ, dessen erwarteter Wert eine Zeichenfolge ist, deren Textinhalt dem gleichen Inhalt des Zeichenfolgenliteral entspricht."

Dieses Verhalten kann geändert werden. TypeScript 3.4 führt eine interessante neue Funktion namens const assertions ein, die die Verwendung eines Konstrukts ermöglicht as const. So sieht seine Verwendung aus:

let fruit = 'Banana' as const;

Jetzt ist fruites ein String-Literal. Das Design as constist auch praktisch, wenn eine Entität unveränderlich gemacht werden muss. Betrachten Sie das folgende Objekt:

const user = {
  name: 'John',
  role: 'admin'
};

In JavaScript constbedeutet das Schlüsselwort , dass Sie nicht überschreiben können, was in einer Konstante gespeichert ist user. Andererseits können Sie die interne Struktur eines in dieser Konstante aufgezeichneten Objekts ändern.

Jetzt speichert das Objekt die folgenden Typen:

const user: {
  name: string,
  role: string
};

Damit das System dieses Objekt als unveränderlich wahrnimmt, können Sie Folgendes verwenden as const:

const user = {
  name: 'John',
  role: 'admin'
} as const;

Jetzt haben sich die Typen geändert. Strings wurden zu String-Literalen, nicht zu gewöhnlichen Strings. Aber nicht nur das hat sich geändert. Jetzt sind die Eigenschaften schreibgeschützt:

const user: {
  readonly name: 'John',
  readonly role: 'admin'
};

Und wenn wir mit Arrays arbeiten, eröffnen sich uns noch mächtigere Möglichkeiten:

const list = ['one', 'two', 3, 4];

Der Typ dieses Arrays ist (string | number)[]. Mit diesem Array können as constSie es in ein Tupel verwandeln:

const list = ['one', 'two', 3, 4] as const;

Der Typ dieses Arrays sieht nun folgendermaßen aus:

readonly ['one', 'two', 3, 4]

All dies gilt für komplexere Strukturen. Betrachten Sie das Beispiel, das Anders Halesberg in seiner Rede auf der TSConf 2019 gegeben hat :

const colors = [
  { color: 'red', code: { rgb: [255, 0, 0], hex: '#FF0000' } },
  { color: 'green', code: { rgb: [0, 255, 0], hex: '#00FF00' } },
  { color: 'blue', code: { rgb: [0, 0, 255], hex: '#0000FF' } },
] as const;

Unser Array ist colorsjetzt vor Änderungen geschützt, und seine Elemente sind auch vor Änderungen geschützt:

const colors: readonly [
    {
        readonly color: 'red';
        readonly code: {
            readonly rgb: readonly [255, 0, 0];
            readonly hex: '#FF0000';
        };
    },
    /// ...
]

Zusammenfassung


In diesem Artikel haben wir uns einige Beispiele für die Verwendung erweiterter Typinferenzmechanismen in TypeScript angesehen. Das Schlüsselwort inferund der Mechanismus werden hier verwendet as const. Diese Tools können in besonders schwierigen Situationen sehr nützlich sein. Zum Beispiel, wenn Sie mit unveränderlichen Entitäten arbeiten müssen oder wenn Sie Programme in einem funktionalen Stil schreiben. Wenn Sie sich weiterhin mit diesem Thema vertraut machen möchten, schauen Sie sich dieses Material an.

Liebe Leser! Verwenden Sie Schlüsselwörter inferund Konstruktionen as constin TypeScript?


All Articles