28 aprile 2025
Padroneggiare le funzionalità avanzate di TypeScript per applicazioni complesse
TypeScript è un linguaggio estremamente potente che va ben oltre le semplici annotazioni di tipo. Con l’aumentare della complessità delle tue applicazioni, le funzionalità avanzate di TypeScript ti forniscono gli strumenti necessari per scrivere codice più sofisticato, flessibile e affidabile.
In questo articolo esploreremo alcune delle funzionalità avanzate di TypeScript che ti aiuteranno a gestire scenari complessi. Vedremo i Type Guard, i Mapped Type, i Conditional Type, gli Utility Type e le Discriminated Union.
Type Guard: restringere i tipi con sicurezza
In TypeScript, un type guard è una tecnica utilizzata per restringere il tipo di una variabile. I type guard permettono di scrivere logica più specifica basata sul tipo reale di un valore.
Utilizzo dei Type Guard incorporati
Operatore typeof
Puoi usare l’operatore typeof
per controllare i tipi primitivi:
function printLength(value: string | number) {
if (typeof value === "string") {
console.log(value.length); // TypeScript knows `value` is a string here
} else {
console.log(value.toFixed(2)); // `value` is a number here
}
}
In questo esempio, TypeScript restringe automaticamente il tipo di value a string o number in base al controllo effettuato con typeof
.
Operatore instanceof
L’operatore instanceof
verifica se un oggetto è un’istanza di una classe specifica:
class Animal {
move() {}
}
class Bird extends Animal {
fly() {}
}
function makeSound(animal: Animal) {
if (animal instanceof Bird) {
animal.fly(); // TypeScript knows `animal` is a `Bird` here
}
}
Qui, instanceof
permette a TypeScript di riconoscere esattamente il tipo di animal quando è un Bird
, fornendo tipi più precisi per metodi come fly()
.
Type Guard personalizzati
Puoi creare type guard personalizzati per affinare ulteriormente la logica:
interface Dog {
type: "dog";
bark(): void;
}
interface Cat {
type: "cat";
meow(): void;
}
function isDog(animal: Dog | Cat): animal is Dog {
return animal.type === "dog";
}
const pet: Dog | Cat = { type: "dog", bark: () => {} };
if (isDog(pet)) {
pet.bark(); // TypeScript knows `pet` is a `Dog` here
}
La funzione isDog
è una type guard personalizzata che restringe il tipo di animal a Dog
se il suo tipo è “dog”.
Mapped Type: trasformare le proprietà dei tipi
I Mapped Type ti permettono di creare un nuovo tipo trasformando le proprietà di un tipo esistente.
Mapped Type di base
Ecco come creare un tipo che rende tutte le proprietà opzionali:
type User = { name: string; age: number };
type PartialUser = { [K in keyof User]?: User[K] }; // All properties are optional
keyof User
restituisce l’unione delle proprietà ("name" | "age"
).[K in keyof User]
itera su ogni proprietà per creare un nuovo tipo con proprietà opzionali.
Mapped Type più complessi
Puoi anche trasformare le proprietà in readonly:
type ReadOnlyUser = { readonly [K in keyof User]: User[K] }; // Makes all properties read-only
Questo crea un tipo ReadOnlyUser
, in cui ogni proprietà è contrassegnata come readonly (sola lettura).
Tipi Condizionali: Tipi Dipendenti da Condizioni
I Tipi Condizionali ti permettono di definire tipi basati su determinate condizioni. Questi tipi vengono spesso utilizzati per creare codice più dinamico e flessibile.
Tipi Condizionali di Base
Un tipo condizionale assume la forma T extends U ? X : Y
. Se il tipo T
estende U
, il risultato sarà X
; altrimenti sarà Y
.
type IsString<T> = T extends string ? "Yes" : "No";
type Test1 = IsString<string>; // "Yes"
type Test2 = IsString<number>; // "No"
Tipi Condizionali con infer
Puoi utilizzare la parola chiave infer
per dedurre un tipo all’interno di un controllo condizionale:
type ReturnType<T> = T extends (args: any[]) => infer R ? R : never;
type FunctionReturnType = ReturnType<() => string>; // string
Qui, infer R
estrae il tipo di ritorno di una funzione, e ReturnType
viene definito in base al fatto che il tipo di input sia o meno una funzione.
Tipi di Utilità: Modificatori di Tipo Integrati
TypeScript offre diversi tipi di utilità integrati per rendere il lavoro con i tipi più semplice e flessibile.
Partial<T>
Partial<T>
rende tutte le proprietà di T
opzionali:
interface User {
name: string;
age: number;
}
const partialUser: Partial<User> = {}; // Both `name` and `age` are optional
Required<T>
Required<T>
rende tutte le proprietà di T
obbligatorie:
interface User {
name?: string;
age?: number;
}
const fullUser: Required<User> = { name: "Alice", age: 30 }; // Both `name` and `age` are now required
Readonly<T>
Readonly<T>
rende tutte le proprietà di T
read-only:
const user: Readonly<User> = { name: "Alice", age: 30 };
// user.age = 31; // Error: Cannot assign to 'age' because it is a read-only property
Altri tipi di utilità
Pick<T, K>
: Seleziona proprietà specifiche da un tipo.Omit<T, K>
: Rimuove proprietà specifiche da un tipo.Record<K, T>
: Crea un tipo oggetto con chiavi di tipo K e valori di tipo T.
Questi tipi di utilità semplificano il lavoro con tipi complessi e migliorano la manutenibilità.
Unioni Discriminatorie: Utilizzare una Proprietà Letterale Comune per Distinguere i Tipi
Un’unione discriminata è una potente funzionalità di TypeScript che combina più tipi in uno, utilizzando una proprietà comune per discriminare tra i tipi.
Esempio: Unione Discriminata con un Tipo Letterale
interface Dog {
type: "dog";
bark(): void;
}
interface Cat {
type: "cat";
meow(): void;
}
type Animal = Dog | Cat;
function makeSound(animal: Animal) {
if (animal.type === "dog") {
animal.bark();
} else {
animal.meow();
}
}
In questo caso, la proprietà di tipo è la discriminante che TypeScript utilizza per restringere il tipo all’interno dell’unione Animal
. Questo rende il lavoro con più tipi molto più semplice e sicuro dal punto di vista del tipo.
Conclusione
Padroneggiare le funzionalità avanzate di TypeScript ti consente di scrivere codice altamente flessibile e sicuro per applicazioni complesse. Rivediamo le caratteristiche principali che abbiamo esplorato:
- Guardi di Tipo: Restringi i tipi utilizzando
typeof
,instanceof
o predicati personalizzati. - Tipi Mappati: Trasforma le proprietà di tipi esistenti.
- Tipi Condizionali: Definisci tipi in base a condizioni con flessibilità.
- Tipi di Utilità: Sfrutta gli helper integrati come
Partial
,Required
,Readonly
e altri. - Unioni Discriminatorie: Usa una proprietà comune per distinguere tra tipi e semplificare il restringimento dei tipi.
Incorporare queste funzionalità avanzate nel tuo flusso di lavoro TypeScript renderà il tuo codice più potente, manutenibile e resistente agli errori. Con la pratica, questi strumenti ti permetteranno di affrontare anche gli scenari più complessi con sicurezza.