I generici forniscono pezzi di codice riutilizzabili che funzionano con una serie di tipi invece che con un singolo tipo. I generici forniscono un modo per trattare il tipo come una variabile e specificarlo al momento dell'uso, in modo simile ai parametri delle funzioni.
I generici possono essere usati insieme alle funzioni (creando funzioni generiche), alle classi (creando classi generiche) e alle interfacce (creando interfacce generiche).
Utilizzo di base
Probabilmente avete usato i generici in passato anche senza saperlo: l'uso più comune dei generici è la dichiarazione di un array:
const myArray: stringa[];
A prima vista non è nulla di speciale, ci limitiamo a dichiarare myArray
come array di stringhe, ma è uguale a una dichiarazione generica:
const myArray: Array;
Mantenere le cose esplicite
Cominciamo con un esempio molto semplice: come possiamo portare questa funzione JS vanilla su TypeScript:
function getPrefiledArray(filler, length) {
return (new Array(length)).fill(filler);
}
Questa funzione restituisce un array riempito con la quantità data di riempimento
, quindi lunghezza
sarà numero
e l'intera funzione restituirà un array di riempimento
- ma cos'è il riempimento? A questo punto può essere qualsiasi cosa, quindi un'opzione è quella di utilizzare qualsiasi
:
function getPrefiledArray(filler: any, length: number): any[] {
return (new Array(length)).fill(filler);
}
Utilizzo qualsiasi
è certamente generico - possiamo passare letteralmente qualsiasi cosa, quindi "lavorare con un certo numero di tipi invece che con un singolo tipo" dalla definizione è pienamente coperto, ma perdiamo la connessione tra riempimento
e il tipo di ritorno. In questo caso, vogliamo restituire una cosa comune e possiamo definire esplicitamente questa cosa comune come parametro tipo:
function getPrefiledArray(filler: T, length: number): T[] {
return (new Array(length)).fill(filler);
}
e utilizzare in questo modo:
const prefilledArray = getPrefiledArray(0, 10);
Vincoli generici
Esaminiamo casi diversi, probabilmente più comuni. Perché usiamo i tipi nelle funzioni? Per me, è per garantire che gli argomenti passati alla funzione abbiano alcune proprietà con cui voglio interagire.
Ancora una volta proviamo a portare una semplice funzione JS vanilla in TS.
funzione getLength(thing) {
restituisce thing.length;
}
Abbiamo un enigma non banale: come garantire che la cosa abbia un lunghezza
e il primo pensiero potrebbe essere quello di fare qualcosa di simile:
function getLength(thing: typeof Array):number {
return thing.length;
}
e a seconda del contesto potrebbe essere corretto, nel complesso siamo un po' generici - funzionerà con array di più tipi, ma cosa succede se non sappiamo se l'oggetto deve essere sempre un array - forse l'oggetto è un campo da calcio o una buccia di banana? In questo caso, dobbiamo raccogliere le proprietà comuni di quell'oggetto in un costrutto che possa definire le proprietà di un oggetto - un'interfaccia:
interfaccia IThingWithLength {
lunghezza: numero;
}
Possiamo utilizzare IThingConLunghezza
come tipo dell'interfaccia cosa
parametro:
function getLength(thing: IThingWithLength):number {
restituisce thing.length;
}
francamente in questo semplice esempio andrà benissimo, ma se vogliamo mantenere questo tipo generico e non affrontare il problema del primo esempio possiamo usare Vincoli generici:
function getLength(thing: T):number {
restituisce thing.length;
}
e utilizzarlo:
interfaccia IBananaPeel {
spessore: numero;
lunghezza: numero;
}
const bananaPeel: IBananaPeel = {spessore: 0,2, lunghezza: 3,14};
getLength(bananaPeel);
Utilizzo si estende
assicura che T
conterrà proprietà definite da IThingConLunghezza
.
Classi generiche
Fino a questo punto, abbiamo lavorato con funzioni generiche, ma non è l'unico posto in cui i generici brillano: vediamo come incorporarli nelle classi.
Prima di tutto, proviamo a conservare un mucchio di banane nel cestino delle banane:
classe Banana {
costruttore(
public lunghezza: numero,
public colore: stringa,
public radiazione ionizzante: numero
) {}
}
class BananaBasket {
private banane: Banana[] = [];
add(banana: Banana): void {
this.bananas.push(banana);
}
}
const bananaBasket = new BananaBasket();
bananaBasket.add(new Banana(3.14, 'red', 10e-7));
Ora proviamo a creare un cestino generico, per diverse cose con lo stesso tipo:
classe Cestino {
private stuff: T[] = [];
add(thing: T): void {
this.stuff.push(thing);
}
}
const bananaBasket = new Basket();
Infine, supponiamo che il nostro cestino sia un contenitore di materiale radioattivo e che si possa immagazzinare solo materia che abbia radiazioni ionizzanti
proprietà:
interfaccia IRadioactive {
ionizingRadiation: numero;
}
class RadioactiveContainer {
private stuff: T[] = [];
add(thing: T): void {
this.stuff.push(thing);
}
}
Interfaccia generica
Infine, cerchiamo di raccogliere tutte le nostre conoscenze e di costruire un impero radioattivo utilizzando anche le interfacce generiche:
// Definire gli attributi comuni per i contenitori
interfaccia IRadioactive {
ionizingRadiation: number;
}
// Definisce qualcosa che è radioattivo
interfaccia IBanana estende IRadioactive {
lunghezza: numero;
colore: stringa;
}
// Definisce qualcosa che non è radioattivo
interfaccia IDog {
peso: numero;
}
// Definisce un'interfaccia per un contenitore che può contenere solo materiale radioattivo
interface IRadioactiveContainer {
add(thing: T): void;
getRadioactiveness():number;
}
// Definire la classe che implementa l'interfaccia del contenitore radioattivo
class RadioactiveContainer implements IRadioactiveContainer {
private stuff: T[] = [];
add(thing: T): void {
this.stuff.push(thing);
}
getRadioactiveness(): number {
return this.stuff.reduce((a, b) => a + b.ionizingRadiation, 0)
}
}
// ERRORE! Il tipo 'IDog' non soddisfa il vincolo 'IRadioactive'.
// Ed è piuttosto brutale memorizzare i cani all'interno del contenitore radioattivo
const dogsContainer = new RadioactiveContainer();
// Tutto bene!
const radioactiveContainer = new RadioactiveContainer();
// Ricordarsi di differenziare i rifiuti radioattivi - creare un contenitore separato solo per le banane
const bananasContainer = new RadioactiveContainer();
Questo è tutto, gente!