Typevoorwaarden zijn een interessant syntactisch kenmerk in TypeScript. Ze verschijnen op dezelfde plaats als annotaties voor returntypes, maar lijken meer op korte bevestigende zinnen dan op typische annotaties. Dit geeft u meer controle over typenavigatie.

Met de release van TypeScript 5.5 is werken met typevoorwaarden intuïiever geworden, omdat het ze nu automatisch kan afleiden in veel gevallen. Maar als u door oudere code-basissen navigeert, zult u waarschijnlijk vaker handgeschreven typevoorwaarden tegenkomen.

In dit artikel zullen we kort bekijken wat typevoorwaarden zijn en waarom ze nuttig zijn. Laten we beginnen met het probleem dat ze oplossen.

Het Probleem

De beste manier om de nuttigheid van typevoorwaarden te begrijpen, denk ik, is door de problemen waar we zonder ze tegenaan lopen op te merken:

function isString(value: unknown): boolean {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string | number
  }
  return " ".repeat(padding) + input; // Oeps typefout hier
                 //   ^
                 // string | number
}

Hier heeft de returntype van isString de waarde boolean en gebruiken we het in een functie genaamd padLeft om padding aan de linkerzijde van een invoerstring toe te voegen. De padding kan ofwel een gegeven string zijn ofwel een gespecificeerd aantal spatie tekens.

Vertaling:

U bent misschien achterdochtig waarom ik het retourtype hardgecodeerd heb naar boolean. Dit is om het probleem uit te leggen. Als u geen annotatie voor het retourtype toevoegt en de nieuwste versie van TypeScript gebruikt, zult u hier geen probleem merken. Voor nu, geduld met mij – we zullen spoedig over de versie-gerelateerde verschillen praten.

De functie werkt zonder problemen tijdens runtime, maar TypeScript kan geen typevernauwing uitvoeren met isString. Als gevolg daarvan blijft het type van padding binnen en buiten de if-instructie string | number. Dit veroorzaakt een conflict met de verwachting van repeat voor zijn eerste argument, wat leidt tot het typefout.

De Oplossing: Typische Predicaten

Misschien bent u niet vertrouwd met de term predicaten, maar u heeft ze waarschijnlijk al gebruikt. Predicaten in programmeren zijn eenvoudigweg functies die een boolean teruggeven om een ja/nee-vraag te beantwoorden. Verscheidene ingebouwde JavaScript-arraymethoden, zoals filter, find, every en some, gebruiken predicaten om bij het nemen van beslissingen te helpen.

Type predicaten zijn een manier om predicaten nuttiger te maken voor typevernauwing. We kunnen het probleem oplossen door een type predicaten als retourtype te gebruiken:

function isString(value: unknown): value is string {
  return typeof value === "string";
}

Here het type predicaten is value is string. Het zegt drie dingen:

  • De functie is een predicaten. dus TypeScript zal een fout weergeven als u probeert iets anders dan een Booleaanse waarde terug te geven.

  • Als het true teruggeeft, is value van het type string.

  • Als het false teruggeeft, is value niet van het type string.

Typepredicaten laten u gebruiker-gedefinieerde typeguards maken. Typeguards zijn logische checks die u toelaten om types te verfijnen naar meer specifieke types, ook wel bekend als verkleinen. Dus, de bovenstaande functie is ook een gebruiker-gedefinieerde typeguard.

Hier is de volledige code:

function isString(value: unknown): value is string {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string
  }
  return " ".repeat(padding) + input;
                 //   ^
                 // number
}

Hier, TypeScript verfijnt correct het type van padding binnen de if-statement en buiten hetzelfde.

Laat ons nu kort kijken naar hoe typepredicaten werkten voor TypeScript 5.5 en wat deze versie verbeterd heeft.

Type Predicaten Voor TypeScript 5.5

In ons vorige voorbeeld, als we geen returntype specificeren, zal het worden geïnfered als boolean:

function isString(value: unknown) {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // string | number
  }
  return " ".repeat(padding) + input; // Opps type error hier
                 //   ^
                 // string | number
}

We hebben dezelfde fout als wanneer we handmatig het return type boolean schreven. Hier is de TypeScript playground-link voor het bovenstaande codefragment. Ga naar de functies of variabelen voor een beter gevoel van de typen. Vervolgens zie hoe het schrijven van het type predicaten het probleem oplost.

Als we het type predicaten niet specificeren, kan het gebruik van methoden zoals filter ook leiden tot een incorrecte type detectie:

function isString(value: unknown) {
  return typeof value === "string";
}

const numsOrStrings = [1, 'hello', 2, 'world'];
//      ^
//    strings: (string | number)[]

const strings = numsOrStrings.filter(isString);
//      ^
//    strings: (string | number)[]

Laten we nu zien hoe TypeScript 5.5 de situatie verbetert.

Type Predicaten Naar TypeScript 5.5

Een van de topfuncties van TypeScript 5.5 is het kunnen infereren van type predicaten door de functiebody te analyseren. Dus als je TypeScript 5.5 of later gebruikt, hoef je het type predicaten niet als returntype van isString te schrijven. TypeScript doet het voor je, en code zoals je ziet in het onderstaande voorbeeld werkt perfect goed:

function isString(value: unknown) {
  return typeof value === "string";
}

function padLeft(padding: number | string, input: string) {
  if (isString(padding)) {
    return padding + input;
        //   ^
        // tekenreeks
  }
  return " ".repeat(padding) + input; // Fout type hier
                 //   ^
                 // nummer
}

const numsOrStrings = [1, 'hello', 2, 'world'];

const strings = numsOrStrings.filter(isString);
//      ^
//    tekenreeksen: string[]

const numbers = numsOrStrings.filter((v) => !isString(v));
//      ^
//    getallen: number[]

Ik heb nog geen situatie gevonden waarin ik ontevreden ben over de automatische inferentie van type-predicaten. Als je er wel een vindt, kun je altijd je eigen handmatig schrijven.

Verdere studie

In dit artikel hebben we kort type-predicaten in TypeScript verkend. Als je meer wilt leren en de randgevallen wilt begrijpen, hier zijn de officiële gidsen:

Bedankt voor het lezen! Tot de volgende keer!

Afbeelding van achtergrond afkomstig van Mona Eendra op Unsplash