Website-Suche

TypeScript-Union aus String und String-Literalen


Ich möchte eine Typdefinition für eines der bekannten Zeichenkettenliterale oder beliebige Zeichenketten erstellen, z. B.:

type IColor = "blue" | "green" | "red" | string;

TS beschwert sich nicht über diese Typdefinition, aber es hilft auch nicht bei Intellisense. Mein Ziel wäre es, eine Funktion zu definieren, die eine der bekannten Farben oder einen beliebigen Farbstring akzeptiert.

const knownColors = {
    green: "#66bb6a",
    red: "#ef9a9a",
    blue: "#81d4fa",
} as const;

function getColor(color: keyof typeof knownColors | string): string {
   if (color in knownColors) 
      return knownColors[color as keyof typeof knownColors];

   return color;
}

Verwendung :

getColor("blue");    // "blue" is a known color, returns "#81d4fa" 
getColor("black");   // "black" is not a known color, returns "black"

Ich möchte, dass IntelliSense intelligent ist

getColor("_          // as I type this line, 
          ^ blue     // I want these suggestions
          ^ green
          ^ red 

Antwort #1

Dies wird derzeit als ein Designlimit (siehe microsoft/TypeScript#29729) und/oder als eine fehlende Funktion (siehe microsoft/TypeScript#33471) von TypeScript betrachtet. Aus der Sicht des Typsystems ist string ein Obertyp von beliebigem String-Literal wie "blue" oder "red", und daher ist eine Vereinigung string | "blue" | "red" dasselbe wie string, und der Compiler reduziert solche Vereinigungen aggressiv auf string. Dies ist vollkommen korrekt, was die Typsicherheit betrifft. Aber aus Sicht der Dokumentation oder IntelliSense ist es, wie Sie gesehen haben, nicht ideal.

Glücklicherweise schlagen die verlinkten TypeScript-Probleme einige Umgehungslösungen vor, die für Sie nützlich sein könnten. Eine besteht darin, den Union-Typ so darzustellen, dass der Compiler ihn nicht aggressiv reduziert. Der Typ string & {} ist konzeptionell derselbe wie string, da der leere Typ {} mit jedem nicht-null und nicht-undefined Typ übereinstimmt. Aber der Compiler führt diese Reduktion nicht durch (zumindest ab TS 3.8). Aus diesem Typ können Sie Ihre Union wie (string & {}) | "rot" | "grün" | "blau" aufbauen, und der Compiler behält diese Darstellung lange genug bei, um Ihnen IntelliSense-Hinweise zu geben:

function getColor(color: (keyof typeof knownColors) | (string & {})): string {
    if (color in knownColors)
        return knownColors[color as keyof typeof knownColors];
    return color;
}

Dies akzeptiert und lehnt die gleichen Eingaben wie zuvor ab:

getColor("red"); // okay
getColor("mauve"); // okay
getColor(123); // error

Aber Sie können überprüfen, dass IntelliSense Folgendes erzeugt:

Die Typsignatur könnte etwas verwirrender sein, als Sie möchten. Sie könnten auch einen ähnlichen Effekt erzielen, indem Sie stattdessen Überladungen verwenden:

function getColorOvld(color: (keyof typeof knownColors)): string;
function getColorOvld(color: string): string;
function getColorOvld(color: string): string {
    if (color in knownColors)
        return knownColors[color as keyof typeof knownColors];
    return color;
}

getColorOvld("red"); // okay
getColorOvld("mauve"); // okay
getColorOvld(123); // error

Dies gibt Ihnen auch angemessenes IntelliSense:

Okay, ich hoffe, das hilft!

Playground-Link zum Code

Antwort #2

Andeutungsstring

Unten ist ein Typ basierend auf der Antwort von @jcalz, um eine gezeigte Zeichenkettenfunktionalität bereitzustellen.

/**
 * Constructs a string type with type hints for known values.
 */
// eslint-disable-next-line @typescript-eslint/ban-types
type HintedString<KnownValues extends string> = (string & {}) | KnownValues;

let nodeId: HintedString<':root' | ':trash'> = ':root';

nodeId = '454v3tv3';

Spielplatz

Beachten Sie jedoch, dass eine solche "angezeigte Zeichenkette" nicht typensicher ist und nicht empfohlen wird, in öffentlichen Definitionen verwendet zu werden, die im gesamten Codebasis genutzt werden. D.h. im obigen Beispiel, wenn ich root in etwas anderes umbenennen wollte, würde ich keine Hinweise von TypeScript erhalten.

Typ-sichere Alternativen

Objekt-Wrapper

Das universellste wäre, den String-Typ in einem Objekt oder einem Array zu verpacken, was zwar möglicherweise nicht schön aussieht, aber typen-sicher ist.

d.h.

type Node = ':root' | ':trash' | [string]
// or
type Node = ':root' | ':trash' | { id: string }

Vorlagenliteraltyp

In bestimmten Fällen steht eine viel schönere Lösung zur Verfügung - Template-Literale.

type NodeId = `node_${string}`
type Target = ':root' | ':trash' | NodeId

let target: Target;

target = ''; // error
target = 'node_3r3rfewfwf'; // ok
target = ':root'; // ok

Spielplatz

Der zusätzliche Vorteil in diesem Fall ist, dass der Typ NodeId aussagekräftiger ist als nur ein Alias für String und zusätzliche Typüberprüfungen erzwingt. Natürlich macht es Sinn, ihn nur in bestimmten Situationen anzuwenden.

Verwandte Artikel