CONTATTACI

Guide per aspiranti programmatori

sviluppatrice che levita a gambe incrociate con una moltitudine di oggetti che la circondano
Lezione 25 / 30

Manipolazione di tipi in Typescript

A questo punto, abbiamo tutti gli strumenti per usare il type system come un vero e proprio linguaggio di programmazione a compile time. In questa sezione vedremo come creare tipi a partire da altri tipi, sfruttando vincoli e condizioni per creare complesse strutture a partire da pochi tipi di riferimento o persino da dati JavaScript non tipizzati. 

Iniziamo subito estendendo quello che abbiamo visto sugli object types

Ti avvertiamo: sarà una carrellata! In questa sezione della guida Typescript in italiano andiamo semplicemente a dimostrare le infinite possibilità offerte dal type system, senza individuare esempi e casi d’uso approfonditi per ognuna di esse, perché sarebbe pesantissimo e poco interessante; sarà comunque molto utile avere una panoramica completa di come si può assumere un controllo molto capillare dei tipi per una vasta varietà di situazioni specifiche. Cominciamo!

Usando il punto di domanda ? possiamo indicare su un’interfaccia o un object literal che una certa proprietà può anche non essere presente. Implicitamente, tale proprietà diventerà potenzialmente undefined:

interface OptionalProp { 
 prop?: string; 
} 
const a: OptionalProp = {}; 
const b: OptionalProp = { prop: "hello" }; 
const c: OptionalProp = { prop: undefined };

Indicare una proprietà come opzionale non equivale a indicarla come | undefined: in quel caso, saremmo comunque obbligati a inizializzarla come in c, mentre l’inizializzazione di a non sarebbe valida.

Possiamo anche annotare alcune proprietà di un oggetto come di sola lettura con la parola chiave readonly

interface ReadonlyProp { 
 readonly prop: string; 
}

const a: ReadonlyProp = { prop: "hello" }; 
a.prop = "hi"; // Cannot assign to 'prop' because it is a read-only property.

Se non conosciamo tutte le proprietà di un oggetto ma ne conosciamo la forma, possiamo indicarlo con una firma d’indice (in inglese: index signature):

interface IndexSignature { 
 [key: string]: number; 
} 
const a: IndexSignature = { 
 someProp: 21, 
 someOtherProp: 42 
};

Le firme d’indice possono essere applicate insieme alla definizione di altre proprietà, a patto che queste rispettino tale firma:

interface IndexSignature { 
 [key: string]: number; 
 prop1: number; 
 prop2: string; // Property 'prop2' of type 'string' is not assignable to 'string' index type 'number'. 
}

Partendo da un tipo oggetto possiamo ricavare il tipo delle sue proprietà usando la notazione oggetto[“chiave”]:

interface SomeProps { 
 num: number; 
 str: string; 
 bool: boolean; 
} 
type Num = SomeProps["num"]; // Num = number 
type Str = SomeProps["str"]; // Str = string 
type Bool = SomeProps["bool"] // Bool = boolean

Possiamo fare la stessa cosa con le tuple, indicando l’indice numerico al posto della chiave:

type Tuple = [number, string, boolean];

type Num = Tuple[0]; // Num = number
type Str = Tuple[1]; // Str = string
type Bool = Tuple[2] // Bool = boolean

Con l’operatore keyof otteniamo l’unione delle chiavi di un object type:

interface SomeProps {
  num: number;
  str: string;
  bool: boolean;
}

type Key = keyof SomeProps; // Key = "num" | "str" | "bool"

Possiamo fare l’operazione inversa, indicando un oggetto che ha come chiavi le stringhe di un union type; tali tipi sono detti mapped types:

type Keys = "prop1" | "prop2" | "prop3";

type KeyNumber = {
  [K in Keys]: number;
}

const a: KeyNumber = {
  prop1: 1,
  prop2: 2,
  prop3: 3
};

Possiamo applicare delle condizioni sui tipi usando la parola chiave extends insieme all’operatore ternario:

type Keys = "num" | "str" | "bool";

type KeyValue = {
  [K in Keys]: K extends "num"
    ? number
    : K extends "str"
    ? string
    : boolean;
}

const a: KeyValue = {
  num: 1,
  str: "hello",
  bool: false
};

Un mapped type non può essere definito con un’interfaccia, ma solo come type alias. Qui K funziona come un parametro di tipo che itera tra le possibili opzioni dell’unione

Tramite i mapped types possiamo applicare i modificatori delle proprietà appena visti e persino applicare condizioni per ricavare tipi oggetto da altri tipi oggetto:

interface Model {
  readonly id: number;
  createdAt: Date;
  name: string;
  description?: string;
}

// rende readonly tutte le proprietà
type ReadonlyModel = {
  readonly [K in keyof Model]: Model[K];
};

// rende opzionali tutte le proprietà
type AllOptionalModel = {
  [K in keyof Model]?: Model[K];
};

// rende non-readonly tutte le proprietà
type AllMutableModel = {
  -readonly [K in keyof Model]: Model[K];
};

// rende non-opzionali tutte le proprietà
type AllRequiredModel = {
  [K in keyof Model]-?: Model[K];
};

// rende booleane tutte le proprietà
type AllBooleanModel = {
  [K in keyof Model]: boolean;
};

// rimuove tutte le proprietà che non hanno tipo 'number'
type OnlyNumberPropsModel = {
  [K in keyof Model as Model[K] extends number ? K : never]: Model[K];
};

I tipi condizionali possono essere utilizzati anche in combinazione con i generics:

type NumberOrNever<T> = T extends number ? T : never;

type A = NumberOrNever<12>; // A = 12
type B = NumberOrNever<string>; // B = never
type C = NumberOrNever<"hello">; // C = never
type D = NumberOrNever<number | string>; // D = number

Per concludere, vediamo come possiamo ricavare tipi dai dati JavaScript. 

La parola chiave typeof espone il tipo di una variabile:

const two = 2;
type Two = typeof two; // Two = 2

let num = 1;
type Num = typeof num; // Num = number

const user = {
  name: "John Doe",
  email: "john.doe@example.com",
  phone: "345 678 9012"
};

type User = typeof user;
// User = {
//   name: string;
//   email: string;
//   phone: string;
// }

Questo può essere molto comodo quando si vuole definire un certo oggetto e il suo tipo contemporaneamente. 

Va notato che quando una variabile o proprietà è modificabile, TypeScript le assegna un tipo non letterale, mentre se è costante le assegna il tipo letterale corrispondente al suo valore.

Se volessimo trattare un intero oggetto o array come immutabile per ricavarne esattamente il tipo letterale, possiamo usare as const; vediamo come cambiano le definizioni precedenti:

const two = 2; // as const qui non serve
type Two = typeof two; // Two = 2

let num = 1 as const;
type Num = typeof num; // Num = 1

const user = {
  name: "John Doe",
  email: "john.doe@example.com",
  phone: "345 678 9012"
} as const;

type User = typeof user;
// User = {
//   readonly name: "John Doe";
//   readonly email: "john.doe@example.com";
//   readonly phone: "345 678 9012";
// }

Le notazioni viste fin qui ci permettono di manipolare con chirurgica precisione ciò che vogliamo prelevare da un tipo o persino da un dato preesistente e come vogliamo modificarlo; tuttavia, non è verosimile pensare di usarle frequentemente visto che sono tante e piuttosto complesse e, dunque, non favoriscono particolarmente la leggibilità e la comprensione dei nostri tipi. La cosa più verosimile è che tali notazioni siano utilizzate in modo isolato per definire dei tipi generici utili per applicare le modifiche viste ai tipi esistenti nel nostro dominio

Nella sezione seguente vedremo che la maggior parte di questi generici utili ci viene fornita nativamente da TypeScript.

Contattaci senza impegno per informazioni sul corso

Pagamento rateale

Valore della rata: A PARTIRE DA 115 €/mese.

Esempio di finanziamento 

Importo finanziato: € 2440 in 24 rate da € 115 – TAN fisso 9,55% TAEG 12,57% – importo totale del credito € 2841.

Il costo totale del credito comprende: interessi calcolati al TAN indicato, oneri fiscali (imposta di bollo sul contratto 16,00 euro*) addebitati sulla prima rata, costo mensile di gestione pratica € 3,90, spesa di istruttoria € 0,00, spesa per invio rendicontazione periodica cartacea € 0,98 (o spesa per invio rendicontazione periodica cartacea € 0,00), imposta di bollo su rendicontazione periodica € 0,00. Modalità di rimborso obbligatoria: addebito diretto su c/c. La scadenza delle rate è determinata dal giorno della liquidazione del contratto; la data di scadenza delle rate è prevista il giorno 15 del mese. L’importo di ciascuna rata comprende una quota di capitale crescente e interessi decrescente secondo un piano di ammortamento “alla francese”. Offerta valida dal 01/01/2024 al 31/12/2024.

Messaggio pubblicitario con finalità promozionale. Per le informazioni precontrattuali richiedere sul punto vendita il documento “Informazioni europee di base sul credito ai consumatori” (SECCI) e copia del testo contrattuale. Salvo approvazione di Sella Personal Credit S.p.A. Aulab S.r.l. opera quale intermediario del credito NON in esclusiva.

*In fase di richiesta del finanziamento verrà proposta la facoltà di selezionare, in alternativa all’imposta di bollo sul contratto di 16,00 euro, l’imposta sostitutiva, pari allo 0,25% dell’importo finanziato.

Pagamento rateale

Valore della rata: A PARTIRE DA 210 €/mese.

Esempio di finanziamento  

Importo finanziato: € 4500 in 24 rate da € 210,03 – TAN fisso 9,68% TAEG 11,97% – importo totale del credito € 5146,55.

Il costo totale del credito comprende: interessi calcolati al TAN indicato, oneri fiscali (imposta di bollo sul contratto 16,00 euro*) addebitati sulla prima rata, costo mensile di gestione pratica € 3,90, spesa di istruttoria € 0,00, spesa per invio rendicontazione periodica cartacea € 0,98 (o spesa per invio rendicontazione periodica cartacea € 0,00), imposta di bollo su rendicontazione periodica € 0,00. Modalità di rimborso obbligatoria: addebito diretto su c/c. La scadenza delle rate è determinata dal giorno della liquidazione del contratto; la data di scadenza delle rate è prevista il giorno 15 del mese. L’importo di ciascuna rata comprende una quota di capitale crescente e interessi decrescente secondo un piano di ammortamento “alla francese”. Offerta valida dal 01/01/2024 al 31/12/2024.

Messaggio pubblicitario con finalità promozionale. Per le informazioni precontrattuali richiedere sul punto vendita il documento “Informazioni europee di base sul credito ai consumatori” (SECCI) e copia del testo contrattuale. Salvo approvazione di Sella Personal Credit S.p.A. Aulab S.r.l. opera quale intermediario del credito NON in esclusiva.

* In fase di richiesta del finanziamento verrà proposta la facoltà di selezionare, in alternativa all’imposta di bollo sul contratto di 16,00 euro, l’imposta sostitutiva, pari allo 0,25% dell’importo finanziato.

Contattaci senza impegno per informazioni sul corso

Scopriamo insieme se i nostri corsi fanno per te. Compila il form e aspetta la chiamata di uno dei nostri consulenti.