I predicati di tipo sono una caratteristica sintattica interessante in TypeScript. mentre appaiono nello stesso posto delle annotazioni del tipo di ritorno, sembrano più come brevi affermazioni che come tipi di annotazioni tipici. Ciò vi dà un maggior controllo sul checking del tipo.

Con l’uscita di TypeScript 5.5, l’utilizzo dei predicati di tipo è diventato ora più intuitivo perché può inferenciarli automaticamente in molti casi. Ma se vi trovate a navigare in code base un po ‘più vecchi, è probabile che spesso si incontrino predicati di tipo scritti a mano.

In questo articolo, esploreremo brevemente cosa sono i predicati di tipo e perché sono utili. Cominciamo osservando il problema che risolvono.

Il Problema

La migliore maniera, penso, per capire l’utilità dei predicati di tipo è notare i problemi che si verificano quando non ce ne sono:

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; // Opps type error here
                 //   ^
                 // string | number
}

Qui, il tipo di ritorno di isString è impostato a boolean, e lo utilizziamo in una funzione chiamata padLeft per aggiungere il riempimento a sinistra di una stringa di input. Il padding può essere sia una stringa specificata che un numero specifico di caratteri spazio.

Potresti chiederti perché ho codificato manualmente il tipo di ritorno a boolean. Questo serve per illustrare il problema. Se non aggiungi alcuna annotazione di tipo di ritorno e usi l’ultima versione di TypeScript, non noterai alcun problema qui. Per ora, abbi pazienza – discuteremo a breve delle differenze legate alla versione.

La funzione funzionerà senza problemi a runtime, ma TypeScript non può eseguire alcun restringimento di tipo con isString. Di conseguenza, il tipo di padding rimane string | number sia all’interno che all’esterno della dichiarazione if. Questo porta a un conflitto con l’aspettativa di repeat per il suo primo argomento, causando l’errore di tipo.

La Soluzione: Introduzione dei Predicati di Tipo

Anche se non hai familiarità con il termine predicato, probabilmente li hai usati prima. I predicati nella programmazione sono semplicemente funzioni che restituiscono un booleano per rispondere a una domanda sì/no. Diversi metodi integrati degli array JavaScript, come filter, find, every, e some, utilizzano i predicati per aiutare nel processo decisionale.

I predicati di tipo sono un modo per rendere i predicati più utili per il restringimento dei tipi. Possiamo risolvere il problema usando un predicato di tipo come tipo di ritorno:

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

Qui il predicato di tipo è value is string. Sta dicendo tre cose:

  • La funzione è un predicato. Quindi TypeScript mostrerà un errore se provi a restituire qualsiasi cosa diversa da un valore Booleano.

  • Se restituisce true, allora value è di tipo stringa.

  • Se restituisce false, allora value non è di tipo stringa.

I predicati di tipo ti consentono di creare guardiani di tipo personalizzati. I guardiani di tipo sono check logici che ti consentono di raffinare i tipi in tipi più specifici, detti anche di restringere. Quindi, la funzione sopra è anche un guardiano di tipo personalizzato.

Ecco il codice completo:

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
}

Qui, TypeScript correttamente restringe il tipo di padding all’interno dell’istruzione if e fuori da essa.

Adesso guardiamo velocemente come funzionavano i predicati di tipo prima di TypeScript 5.5 e quali miglioramenti questa versione ha apportato.

Predicati di Tipo Prima di TypeScript 5.5

Nell’esempio precedente, se non specifici nessun tipo di ritorno, sarà inferito come 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 here
                 //   ^
                 // string | number
}

Conseguiamo quindi lo stesso errore che avevamo quando abbiamo scritto manualmente il tipo di ritorno boolean. Qui link alla playgorund di TypeScript per il fragmento di codice soprastante. Andate e fatehover sulle funzioni o sulle variabili per una migliore percezione dei tipi. Poi vedete come la scrittura del predicato di tipo risolve il problema.

Se non specificiamo il predicato di tipo, l’uso di metodi come filter può anche portare a una detezione di tipo non corretta:

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)[]

Adesso diamo un’occhiata a come TypeScript 5.5 migliora la situazione.

Predicati di Tipo Dopo TypeScript 5.5

Una delle principali caratteristiche di TypeScript 5.5 è la possibilità di inferire predicati di tipo analizzando il corpo della funzione. Quindi se state utilizzando TypeScript 5.5 o successivi, non dovete scrivere il predicato di tipo come tipo di ritorno di isString. TypeScript lo fa per voi, e il codice come quello che vedete nell’esempio sottostante funziona perfettamente:

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

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

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

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

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

Non ho ancora trovato una situazione in cui sono insoddisfatto dell’inferenza automatica dei predicati di tipo. Se ne trovi una, puoi sempre scrivere i tuoi manualmente.

Ulteriore Studio

In questo articolo, abbiamo esplorato brevemente i predicati di tipo in TypeScript. Se sei interessato a saperne di più e a comprendere i casi limite, ecco le guide ufficiali:

Grazie per la lettura! Ci vediamo la prossima volta!

Il sfondo della copertina è di Mona Eendra su Unsplash.