Programmare in TypeScript
I generici TypeScript
Introduzione
Programmare in TypeScript significa applicare la programmazione generica. E’ un potente paradigma che ha rivoluzionato il modo in cui scriviamo il codice, rendendolo più riutilizzabile e flessibile. Secondo Wikipedia, “La programmazione generica è uno stile di programmazione informatica in cui gli algoritmi sono scritti in termini di tipi da specificare in seguito che vengono poi istanziati quando necessario per tipi specifici forniti come parametri”. TypeScript, un popolare superset di JavaScript, ha abbracciato questo concetto di generici, consentendo agli sviluppatori di creare codice manutenibile e scalabile con facilità.
In questo articolo “Programmare in TypeScript“, esploreremo il mondo dei generici TypeScript, facendo luce su come possono migliorare la nostra esperienza di programmazione e aumentare l’efficienza del nostro codice. Comprendendo i fondamenti dei generici, saremo più capaci di scrivere codice pulito e riutilizzabile che può adattarsi a un’ampia gamma di scenari. Quindi, tuffiamoci e scopriamo il vero potenziale dei generici in TypeScript!
Ciao mondo dei generici identifica la funzione
Per comprendere le basi dei generici, prendiamo in esame un semplice esempio: il “Hello World” dei generici, ovvero la funzione identità.
La funzione identità è una funzione che accetta un singolo argomento e lo restituisce invariato. Senza usare generici, potresti scrivere funzioni identità separate per tipi diversi:
const identityNumber = (value: number): number => value;
const identityString = (value: string): string => value;
Tuttavia, questo approccio non è scalabile e porta alla duplicazione del codice 😕. Invece, possiamo usare i generici per creare una singola funzione di identità riutilizzabile che funzioni con qualsiasi tipo:
const identity = (value: T): T => value;
In questo esempio, è una variabile di tipo che rappresenta un tipo ancora da determinare. Utilizzando nella firma della funzione, indichiamo che la funzione identità può funzionare con qualsiasi tipo T. Quando si chiama questa funzione, TypeScript dedurrà il tipo appropriato in base all’argomento fornito:
const result1: number = identity(42);
// T is inferred to be 'number'
const result2: string = identity("Hello World");
// T is inferred to be 'Hello World'
In alternativa, è possibile specificare esplicitamente il tipo quando si chiama la funzione:
const result3: number = identity(42);
const result4: string = identity("Hello World");
Il vantaggio principale dell’utilizzo dei generici in questo caso è che possiamo creare una singola funzione riutilizzabile che funziona con più tipi mantenendo comunque la sicurezza dei tipi . Invece di scrivere funzioni separate per ogni tipo, possiamo utilizzare la funzione di identità generica per gestire vari tipi senza duplicare il codice o sacrificare le informazioni sui tipi.
Un piccolo promemoria: abbiamo già familiarità con i farmaci generici!
Che ci crediate o no, probabilmente avete utilizzato i generici senza nemmeno rendervene conto! Ad esempio, quando lavorate con gli array in TypeScript, state già utilizzando la potenza dei generici.
Considerate il seguente semplice esempio:
const bla: Array = ['a', 'b', 'c'];
In questo caso, TypeScript capisce che bla è un array di stringhe, grazie all’annotazione di tipo Array. Il tipo Array è in realtà un tipo generico, essendo l’argomento di tipo che specifica il tipo di elementi all’interno dell’array.
La bellezza di TypeScript è che spesso è in grado di dedurre il tipo corretto anche se non lo fornisci esplicitamente. Ad esempio:
const bla = ['a', 'b', 'c'];
Senza specificare il tipo, TypeScript riconoscerà comunque che bla è un array di stringhe. Questo perché il sistema di inferenza del tipo di TypeScript esamina gli elementi dell’array e deduce automaticamente che sono tutte stringhe.
Quindi, anche se siamo nuovi al concetto di generici, ricordiamoci che ne abbiamo già raccolto i benefici nel nostro codice TypeScript attraverso costrutti quotidiani come gli array!
Un esempio pratico: caricare un Pokémon con i generici
Ora che conosciamo le basi, facciamo un esempio più pratico per sfruttare tutta la potenza dei generici. Questo è un caso d’uso che incontro spesso nel mio lavoro.
Supponiamo di avere il compito di creare un’interfaccia utente per un Pokédex e che tu debba caricare informazioni su un Pokémon quando un utente clicca su di esso. Una semplice funzione per realizzare questo potrebbe essere:
const loadAPokemon = async (id: number) => {
const url = https://pokeapi.co/api/v2/pokemon/${id};
const response = await fetch(url);
const pokemon = await response.json();
return pokemon;
};
Esaminando la firma del tipo di loadAPokemon, vedresti:
const loadAPokemon: (id: number) => Promise
In scenari reali, probabilmente conosceremmo la struttura della risposta prevista dal backend e utilizzare any come tipo di ritorno non è particolarmente utile.
È qui che entra in gioco il potere dei generici. Per prima cosa, definiamo il tipo di ritorno di un Pokémon:
type Pokemon = {
name: string;
id: number;
type: "grass" | "fire" | "water";
attacks: {
first: string;
second: string;
};
};
Questo semplice tipo rappresenta la risposta che ci aspettiamo di ricevere dall’API Pokémon.
Ora aggiorniamo la nostra funzione per includere i generici, fornendo un tipo di ritorno più preciso:
const loadAPokemon =
async (): Promise => {
// we already know the implementation
};
Ora uno dei principali vantaggi è che ci impedisce di commettere errori di battitura, vediamo:
const main = async () =>
const myPokemon = await loadAPokemon(1);
console.log(myPokemon.name); // Correct property
console.log(myPokemon.name2); // Typo in the property name
};
Miglioramento della funzione loadAPokemon con Extends
Possiamo migliorare ulteriormente la funzione loadAPokemon utilizzando la parola chiave extends in TypeScript. Ciò garantisce che il tipo generico fornito come argomento alla funzione aderisca a una struttura specifica o a un tipo di base. Questo miglioramento può aiutare ad aumentare la sicurezza dei tipi e a prevenire l’uso scorretto della funzione
Per prima cosa, creiamo un tipo base per il nostro Pokémon:
interface PokemonBase {
id: number;
name: string;
}
Ora, aggiorniamo la funzione loadAPokemon per utilizzare la parola chiave extends, assicurandoci che il PokemonType fornito estenda PokemonBase:
const loadAPokemon = async (
id: number
): Promise => {
// we already know the implementation
};
Utilizzando extends, stiamo imponendo che il tipo generico PokemonType debba avere almeno le proprietà definite nell’interfaccia PokemonBase. Questo vincolo garantisce che la funzione verrà utilizzata solo con tipi che soddisfano i requisiti di base di un oggetto Pokémon.
Ad esempio, se proviamo a utilizzare la funzione con un tipo non corretto, TypeScript mostrerà un errore:
type InvalidPokemon = {
id: number;
// Missing 'name' property
};
const main = async () => {
const myPokemon =
await loadAPokemon(1);
// Error: Property 'name' is missing
// in type 'InvalidPokemon'
// but required in type 'PokemonBase'.
};
Sfruttando la parola chiave extends, possiamo rendere la funzione loadAPokemon più solida e sicura, assicurandoci che venga utilizzata con i tipi appropriati e prevenendo potenziali bug nel codice.
In conclusione, la parola chiave extends è una potente funzionalità che può aiutare a imporre vincoli sui tipi generici, rendendo il nostro codice TypeScript ancora più affidabile e gestibile. Combinando questo con la versatilità dei generici, puoi creare funzioni altamente flessibili e sicure per un’ampia gamma di casi d’uso.
Utilizzo di KeyOF per creare funzioni ancora più generiche
Immaginiamo di avere il seguente tipo:
interface Pokemon {
name: string;
type: string;
level: number;
}
e lo useremo in questo modo per memorizzare un array di pokemon
const pokemonTeam: Pokemon[] = [
{ name: "Pikachu", type: "Electric", level: 12 },
{ name: "Charmander", type: "Fire", level: 8 },
{ name: "Squirtle", type: "Water", level: 10 },
];
Ora diciamo che da qualche parte devi filtrare un Pokémon specifico da quello, quindi potremmo avere una funzione e chiamarla così
const pikachu =
findObjectByProperty(pokemonTeam, "name", 'Pikachu');
Con l’aiuto di keyOf e generics possiamo anche rendere questo tipo di funzione sicuro in modo che otteniamo un errore se cerchiamo una chiave che non è nel nostro tipo
Ecco come apparirebbe la funzione in TypeScript
function findObjectByProperty(
arr: T[],
prop: keyof T,
value: T[keyof T]
): T | undefined {
return arr.find((obj) => obj[prop] === value);
}
Ora analizziamo questa implementazione:
La firma della funzione include un tipo generico T che estende un oggetto con una firma di indice stringa
({ [key: string]: any }) . Ciò garantisce che il tipo generico T sia un oggetto con chiavi di tipo stringa.
La funzione accetta tre parametri:
- arr: un array di oggetti di tipo T.
- prop: una chiave di proprietà dell’oggetto, definita come keyof T. La parola chiave keyof recupera le chiavi del tipo di oggetto T e assicura che prop sia una delle chiavi valide.
- valore: il valore da cercare all’interno della proprietà specificata. Il tipo di questo valore è T[keyof T], il che significa che può essere qualsiasi valore delle proprietà nel tipo T.
La funzione restituisce un oggetto di tipo T se trovato, o indefinito se non trovato. Utilizza il metodo Array.prototype.find() per scorrere l’array e restituire il primo oggetto che corrisponde alla proprietà e al valore specificati.
Con questa implementazione, ora possiamo usare la funzione findObjectByProperty() per cercare un Pokémon specifico in modo sicuro:
const pikachu =
findObjectByProperty(pokemonTeam, "name", "Pikachu");
const electricPokemon =
findObjectByProperty(pokemonTeam, "type", "Electric");
const level10Pokemon =
findObjectByProperty(pokemonTeam, "level", 10);
// If you try to search for an invalid property,
// TypeScript will show an error:
const invalidSearch =
findObjectByProperty(pokemonTeam, "invalidProperty", "test");
// Error: Argument of type '"invalidProperty"'
// is not assignable to parameter of type
'"name" | "type" | "level"'.
Conclusione Programmare in TypeScript
I generici in TypeScript consentono agli sviluppatori di scrivere codice riutilizzabile e sicuro per i tipi, aumentando la flessibilità e la manutenibilità delle applicazioni. Comprendendo e sfruttando la potenza dei generici, è possibile creare soluzioni robuste che si adattano a vari scenari mantenendo la sicurezza dei tipi.
In questo articolo “Programmare in TypeScript” abbiamo esplorato le basi dei generici, esempi pratici del loro utilizzo e concetti avanzati come la parola chiave extends e keyof. Incorporando queste tecniche nei nostri progetti TypeScript, possiamo migliorare la nostra esperienza di programmazione e creare codice più efficiente, flessibile e manutenibile.
(fonte)
Innovaformazione, scuola informatica specialistica promuove la cultura dello sviluppo web consapevole ed efficiente. Nell’offerta formativa per aziende trovate il Corso Angular che contiente TypeScript
INFO: info@innovaformazione.net – tel. 3471012275 (Dario Carrassi)
Articoli correlati
Claude Code e Migrazioni SAP
Claude Code controllo remoto
Opportunità Carriera Contabilità SAP
Guida SIA AI
Guida Dual LLM Verification
