Funzioni Parametri Tipizzati

Edoardo Midali
Edoardo Midali

I parametri tipizzati in TypeScript rappresentano il meccanismo fondamentale per garantire type safety nelle funzioni, specificando quali tipi di valori possono essere accettati. La tipizzazione può essere esplicita attraverso annotazioni o inferita dal contesto, permettendo al compilatore di verificare correttezza delle chiamate e prevenire errori a compile time.

Fondamenti e Annotazioni Esplicite

I parametri si tipizzano aggiungendo : Type dopo il nome del parametro. TypeScript richiede tipizzazione esplicita per parametri di funzione quando il tipo non può essere inferito dal contesto.

// Tipizzazione esplicita base
function somma(a: number, b: number): number {
  return a + b;
}

// Multiple parametri con tipi diversi
function saluta(nome: string, eta: number, attivo: boolean): string {
  return `${nome}, ${eta} anni, ${attivo ? "attivo" : "inattivo"}`;
}

// Tipi primitivi
function processa(
  testo: string,
  numero: number,
  flag: boolean,
  simbolo: symbol,
  grande: bigint
): void {
  console.log(testo, numero, flag, simbolo, grande);
}

Tipi Complessi per Parametri

I parametri possono accettare oggetti, array, tuple e altri tipi complessi con annotazioni strutturali.

// Parametro oggetto con struttura inline
function creaUtente(dati: { nome: string; email: string; eta: number }): void {
  console.log(dati);
}

// Parametro array
function elaboraNumeri(numeri: number[]): number {
  return numeri.reduce((sum, n) => sum + n, 0);
}

// Parametro tuple
function coordinate(punto: [number, number]): string {
  return `X: ${punto[0]}, Y: ${punto[1]}`;
}

// Parametro con array di oggetti
function filtraProdotti(
  prodotti: Array<{ nome: string; prezzo: number }>,
  prezzoMax: number
): Array<{ nome: string; prezzo: number }> {
  return prodotti.filter((p) => p.prezzo <= prezzoMax);
}

Interface e Type Alias per Parametri

Usare interface o type alias migliora leggibilità e riutilizzabilità delle tipizzazioni parametri.

// Con interface
interface Utente {
  id: string;
  nome: string;
  email: string;
}

function aggiornaUtente(utente: Utente, modifiche: Partial<Utente>): Utente {
  return { ...utente, ...modifiche };
}

// Con type alias
type Configurazione = {
  porta: number;
  host: string;
  debug: boolean;
};

function avviaServer(config: Configurazione): void {
  console.log(`Server su ${config.host}:${config.porta}`);
}

// Type per funzioni callback
type Callback = (risultato: string) => void;

function esegui(operazione: string, callback: Callback): void {
  const risultato = `Eseguito: ${operazione}`;
  callback(risultato);
}

Union Types per Parametri

I parametri possono accettare multiple tipologie di valori usando union types.

// Union type semplice
function formatta(valore: string | number): string {
  if (typeof valore === "string") {
    return valore.toUpperCase();
  }
  return valore.toFixed(2);
}

// Union con literal types
function setModalita(modo: "dev" | "test" | "prod"): void {
  console.log(`Modalità: ${modo}`);
}

// Union complessa
function processa(input: string | number | { valore: string | number }): void {
  if (typeof input === "object") {
    console.log(input.valore);
  } else {
    console.log(input);
  }
}

Parametri Opzionali

I parametri opzionali si dichiarano con ? e hanno tipo T | undefined.

// Parametro opzionale
function creaUtente(nome: string, cognome?: string): void {
  if (cognome) {
    console.log(`${nome} ${cognome}`);
  } else {
    console.log(nome);
  }
}

creaUtente("Mario");
creaUtente("Mario", "Rossi");

// Multipli parametri opzionali
function configura(porta?: number, host?: string, debug?: boolean): void {
  const p = porta ?? 3000;
  const h = host ?? "localhost";
  const d = debug ?? false;
  console.log({ porta: p, host: h, debug: d });
}

// Parametri opzionali devono seguire quelli obbligatori
function crea(nome: string, eta?: number, citta?: string): object {
  return { nome, eta, citta };
}

Inferenza Contestuale

TypeScript inferisce tipi dei parametri quando la funzione è usata come callback o assegnata a variabile tipizzata.

// Inferenza da context type
const numeri = [1, 2, 3, 4, 5];

// n è inferito come number
numeri.forEach((n) => console.log(n * 2));

// Inferenza con metodi array
const stringhe = numeri.map((n) => n.toString()); // n: number inferito

// Inferenza da type di variabile
type Operazione = (a: number, b: number) => number;

const somma: Operazione = (a, b) => a + b; // a, b inferiti come number

// Inferenza con callback tipizzate
function esegui(callback: (x: number) => string): void {
  callback(42);
}

esegui((x) => x.toFixed(2)); // x inferito come number

Parametri con Readonly

Usare readonly per indicare che la funzione non modificherà il parametro, utile per array e oggetti.

// Array readonly
function somma(numeri: readonly number[]): number {
  // numeri.push(10); // ERRORE: non modificabile
  return numeri.reduce((sum, n) => sum + n, 0);
}

// Oggetto readonly
function visualizza(config: Readonly<{ porta: number; host: string }>): void {
  console.log(config.porta, config.host);
  // config.porta = 8080; // ERRORE: non modificabile
}

// Tuple readonly
function distanza(punto: readonly [number, number]): number {
  const [x, y] = punto;
  return Math.sqrt(x * x + y * y);
}

Generics nei Parametri

I parametri generici permettono funzioni che operano su tipi diversi mantenendo type safety.

// Generic semplice
function primo<T>(array: T[]): T | undefined {
  return array[0];
}

const num = primo([1, 2, 3]); // number | undefined
const str = primo(["a", "b"]); // string | undefined

// Multipli generics
function coppia<T, U>(primo: T, secondo: U): [T, U] {
  return [primo, secondo];
}

const risultato = coppia("hello", 42); // [string, number]

// Generic con constraints
function lunghezza<T extends { length: number }>(item: T): number {
  return item.length;
}

lunghezza("stringa"); // OK
lunghezza([1, 2, 3]); // OK
// lunghezza(123); // ERRORE: number non ha length

Parametri Funzione (Callback)

Tipizzare parametri che sono funzioni per garantire che callback abbiano firma corretta.

// Callback tipizzata inline
function mappa(array: number[], trasforma: (n: number) => number): number[] {
  return array.map(trasforma);
}

// Type alias per callback
type Predicato<T> = (item: T) => boolean;

function filtra<T>(array: T[], predicato: Predicato<T>): T[] {
  return array.filter(predicato);
}

// Callback con multipli parametri
function riduci(
  array: number[],
  reducer: (acc: number, current: number, index: number) => number,
  iniziale: number
): number {
  return array.reduce(reducer, iniziale);
}

// Callback asincrona
async function processoAsync(callback: () => Promise<string>): Promise<void> {
  const risultato = await callback();
  console.log(risultato);
}

Destructuring nei Parametri

I parametri possono usare destructuring con tipizzazione strutturale.

// Destructuring oggetto con tipi
function stampaPersona({
  nome,
  eta,
  citta,
}: {
  nome: string;
  eta: number;
  citta?: string;
}): void {
  console.log(nome, eta, citta);
}

// Con interface
interface Punto {
  x: number;
  y: number;
}

function sposta({ x, y }: Punto, dx: number, dy: number): Punto {
  return { x: x + dx, y: y + dy };
}

// Destructuring array/tuple
function sommaCoordinate([x, y]: [number, number]): number {
  return x + y;
}

// Destructuring annidato
function elabora({
  utente: { nome, email },
  timestamp,
}: {
  utente: { nome: string; email: string };
  timestamp: Date;
}): void {
  console.log(nome, email, timestamp);
}

Index Signatures nei Parametri

Parametri con index signatures permettono oggetti con chiavi dinamiche.

// Oggetto con chiavi dinamiche
function stampaConfig(config: { [chiave: string]: string | number }): void {
  for (const key in config) {
    console.log(`${key}: ${config[key]}`);
  }
}

stampaConfig({ porta: 3000, host: "localhost", timeout: 5000 });

// Record type (più type-safe)
function elaboraDati(dati: Record<string, number>): number {
  return Object.values(dati).reduce((sum, n) => sum + n, 0);
}

// Con generic
function trasformaOggetto<T>(
  obj: Record<string, T>,
  fn: (valore: T) => T
): Record<string, T> {
  const risultato: Record<string, T> = {};
  for (const key in obj) {
    risultato[key] = fn(obj[key]);
  }
  return risultato;
}

This Parameter

TypeScript permette di tipizzare il valore di this per metodi che dipendono da contesto specifico.

// Tipo this esplicito
function saluta(this: { nome: string }): void {
  console.log(`Ciao, ${this.nome}`);
}

const persona = { nome: "Mario" };
saluta.call(persona); // OK
// saluta(); // ERRORE: this deve avere proprietà nome

// This con classe
class Contatore {
  valore = 0;

  incrementa(this: Contatore): void {
    this.valore++;
  }
}

// Arrow function evita necessità di this parameter
class ContatoreSicuro {
  valore = 0;

  incrementa = (): void => {
    this.valore++; // this sempre ContatoreSicuro
  };
}

Parametri con Never Type

Il tipo never per parametri indica funzioni che non dovrebbero essere chiamate in determinati contesti.

// Funzione che non dovrebbe mai essere raggiunta
function erroreInaspettato(msg: never): never {
  throw new Error(`Caso inaspettato: ${msg}`);
}

// Uso in switch exhaustive
type Forma = "cerchio" | "quadrato";

function area(forma: Forma): number {
  switch (forma) {
    case "cerchio":
      return Math.PI * 10 * 10;
    case "quadrato":
      return 10 * 10;
    default:
      return erroreInaspettato(forma); // forma è never qui
  }
}

Assertion Signatures nei Parametri

Parametri con assertion signatures per validazione e type narrowing.

// Assert function
function assertIsString(valore: any): asserts valore is string {
  if (typeof valore !== "string") {
    throw new Error("Non è una stringa");
  }
}

function processa(input: unknown): void {
  assertIsString(input);
  // Dopo assert, input è string
  console.log(input.toUpperCase());
}

// Assert con proprietà
function assertHasId(obj: any): asserts obj is { id: string } {
  if (!obj || typeof obj.id !== "string") {
    throw new Error("Oggetto senza id valido");
  }
}

Variadic Tuple Types

Parametri che accettano tuple di lunghezza variabile con tipi specifici per posizione.

// Tuple variadic con generics
function concat<T extends any[], U extends any[]>(
  arr1: T,
  arr2: U
): [...T, ...U] {
  return [...arr1, ...arr2];
}

const risultato = concat([1, 2], ["a", "b"]);
// [number, number, string, string]

// Pattern con rest elements
function curry<T extends any[], R>(fn: (...args: T) => R): (...args: T) => R {
  return (...args: T) => fn(...args);
}

Conclusioni

La tipizzazione dei parametri in TypeScript fornisce fondamento per type safety, documentazione auto-generata, e migliore developer experience attraverso autocomplete. Annotazioni esplicite garantiscono contratti chiari, mentre inferenza contestuale riduce verbosità. Combinando tipi primitivi, complessi, union, generics, e features avanzate come readonly e assertion signatures, TypeScript permette di esprimere requisiti precisi per parametri, catturando errori a compile time e rendendo il codice più manutenibile e comprensibile senza sacrificare flessibilità.