Italiano

Sblocca la potenza dei tipi utility di TypeScript per scrivere codice più pulito, manutenibile e type-safe. Esplora applicazioni pratiche con esempi reali per sviluppatori di tutto il mondo.

Padroneggiare i Tipi Utility di TypeScript: Una Guida Pratica per Sviluppatori Globali

TypeScript offre un potente set di tipi utility integrati che possono migliorare significativamente la type safety, la leggibilità e la manutenibilità del tuo codice. Questi tipi utility sono essenzialmente trasformazioni di tipo predefinite che puoi applicare ai tipi esistenti, risparmiandoti dalla scrittura di codice ripetitivo e incline agli errori. Questa guida esplorerà vari tipi utility con esempi pratici che risuonano con gli sviluppatori di tutto il mondo.

Perché Usare i Tipi Utility?

I tipi utility affrontano scenari comuni di manipolazione dei tipi. Sfruttandoli, puoi:

Tipi Utility Core

Partial

Partial costruisce un tipo in cui tutte le proprietà di T sono impostate come opzionali. Questo è particolarmente utile quando vuoi creare un tipo per aggiornamenti parziali o oggetti di configurazione.

Esempio:

Immagina di star costruendo una piattaforma di e-commerce con clienti provenienti da diverse regioni. Hai un tipo Customer:


interface Customer {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
  phoneNumber: string;
  address: {
    street: string;
    city: string;
    country: string;
    postalCode: string;
  };
  preferences?: {
    language: string;
    currency: string;
  }
}

Quando aggiorni le informazioni di un cliente, potresti non voler richiedere tutti i campi. Partial ti consente di definire un tipo in cui tutte le proprietà di Customer sono opzionali:


type PartialCustomer = Partial<Customer>;

function updateCustomer(id: string, updates: PartialCustomer): void {
  // ... implementazione per aggiornare il cliente con l'ID dato
}

updateCustomer("123", { firstName: "John", lastName: "Doe" }); // Valido
updateCustomer("456", { address: { city: "London" } }); // Valido

Readonly

Readonly costruisce un tipo in cui tutte le proprietà di T sono impostate come readonly, impedendo la modifica dopo l'inizializzazione. Questo è prezioso per garantire l'immutabilità.

Esempio:

Considera un oggetto di configurazione per la tua applicazione globale:


interface AppConfig {
  apiUrl: string;
  theme: string;
  supportedLanguages: string[];
  version: string; // Added version
}

const config: AppConfig = {
  apiUrl: "https://api.example.com",
  theme: "dark",
  supportedLanguages: ["en", "fr", "de", "es", "zh"],
  version: "1.0.0"
};

Per impedire la modifica accidentale della configurazione dopo l'inizializzazione, puoi usare Readonly:


type ReadonlyAppConfig = Readonly<AppConfig>;

const readonlyConfig: ReadonlyAppConfig = {
  apiUrl: "https://api.example.com",
  theme: "dark",
  supportedLanguages: ["en", "fr", "de", "es", "zh"],
  version: "1.0.0"
};

// readonlyConfig.apiUrl = "https://newapi.example.com"; // Errore: Impossibile assegnare a 'apiUrl' perché è una proprietà di sola lettura.

Pick

Pick costruisce un tipo scegliendo l'insieme di proprietà K da T, dove K è un'unione di tipi letterali stringa che rappresentano i nomi delle proprietà che vuoi includere.

Esempio:

Supponiamo che tu abbia un'interfaccia Event con varie proprietà:


interface Event {
  id: string;
  title: string;
  description: string;
  location: string;
  startTime: Date;
  endTime: Date;
  organizer: string;
  attendees: string[];
}

Se hai bisogno solo di title, location e startTime per un componente di visualizzazione specifico, puoi usare Pick:


type EventSummary = Pick<Event, "title" | "location" | "startTime">;

function displayEventSummary(event: EventSummary): void {
  console.log(`Event: ${event.title} at ${event.location} on ${event.startTime}`);
}

Omit

Omit costruisce un tipo escludendo l'insieme di proprietà K da T, dove K è un'unione di tipi letterali stringa che rappresentano i nomi delle proprietà che vuoi escludere. Questo è l'opposto di Pick.

Esempio:

Usando la stessa interfaccia Event, se vuoi creare un tipo per creare nuovi eventi, potresti voler escludere la proprietà id, che viene tipicamente generata dal backend:


type NewEvent = Omit<Event, "id">;

function createEvent(event: NewEvent): void {
  // ... implementazione per creare un nuovo evento
}

Record

Record costruisce un tipo oggetto le cui chiavi di proprietà sono K e i cui valori di proprietà sono T. K può essere un'unione di tipi letterali stringa, tipi letterali numerici o un simbolo. Questo è perfetto per creare dizionari o mappe.

Esempio:

Immagina di dover memorizzare le traduzioni per l'interfaccia utente della tua applicazione. Puoi usare Record per definire un tipo per le tue traduzioni:


type Translations = Record<string, string>;

const enTranslations: Translations = {
  "hello": "Hello",
  "goodbye": "Goodbye",
  "welcome": "Welcome to our platform!"
};

const frTranslations: Translations = {
  "hello": "Bonjour",
  "goodbye": "Au revoir",
  "welcome": "Bienvenue sur notre plateforme !"
};

function translate(key: string, language: string): string {
  const translations = language === "en" ? enTranslations : frTranslations; //Simplified
  return translations[key] || key; // Fallback to the key if no translation is found
}

console.log(translate("hello", "en")); // Output: Hello
console.log(translate("hello", "fr")); // Output: Bonjour
console.log(translate("nonexistent", "en")); // Output: nonexistent

Exclude

Exclude costruisce un tipo escludendo da T tutti i membri dell'unione che sono assegnabili a U. È utile per filtrare tipi specifici da un'unione.

Esempio:

Potresti avere un tipo che rappresenta diversi tipi di eventi:


type EventType = "concert" | "conference" | "workshop" | "webinar";

Se vuoi creare un tipo che esclude gli eventi "webinar", puoi usare Exclude:


type PhysicalEvent = Exclude<EventType, "webinar">;

// PhysicalEvent è ora "concert" | "conference" | "workshop"

function attendPhysicalEvent(event: PhysicalEvent): void {
  console.log(`Attending a ${event}`);
}

// attendPhysicalEvent("webinar"); // Errore: L'argomento di tipo '"webinar"' non è assegnabile al parametro di tipo '"concert" | "conference" | "workshop"'.

attendPhysicalEvent("concert"); // Valido

Extract

Extract costruisce un tipo estraendo da T tutti i membri dell'unione che sono assegnabili a U. Questo è l'opposto di Exclude.

Esempio:

Usando lo stesso EventType, puoi estrarre il tipo di evento webinar:


type OnlineEvent = Extract<EventType, "webinar">;

// OnlineEvent è ora "webinar"

function attendOnlineEvent(event: OnlineEvent): void {
  console.log(`Attending a ${event} online`);
}

attendOnlineEvent("webinar"); // Valido
// attendOnlineEvent("concert"); // Errore: L'argomento di tipo '"concert"' non è assegnabile al parametro di tipo '"webinar"'.

NonNullable

NonNullable costruisce un tipo escludendo null e undefined da T.

Esempio:


type MaybeString = string | null | undefined;

type DefinitelyString = NonNullable<MaybeString>;

// DefinitelyString è ora string

function processString(str: DefinitelyString): void {
  console.log(str.toUpperCase());
}

// processString(null); // Errore: L'argomento di tipo 'null' non è assegnabile al parametro di tipo 'string'.
// processString(undefined); // Errore: L'argomento di tipo 'undefined' non è assegnabile al parametro di tipo 'string'.
processString("hello"); // Valido

ReturnType

ReturnType costruisce un tipo costituito dal tipo di ritorno della funzione T.

Esempio:


function greet(name: string): string {
  return `Hello, ${name}!`;
}

type Greeting = ReturnType<typeof greet>;

// Greeting è ora string

const message: Greeting = greet("World");

console.log(message);

Parameters

Parameters costruisce un tipo tupla dai tipi dei parametri di un tipo di funzione T.

Esempio:


function logEvent(eventName: string, eventData: object): void {
  console.log(`Event: ${eventName}`, eventData);
}

type LogEventParams = Parameters<typeof logEvent>;

// LogEventParams è ora [eventName: string, eventData: object]

const params: LogEventParams = ["user_login", { userId: "123", timestamp: Date.now() }];

logEvent(...params);

ConstructorParameters

ConstructorParameters costruisce un tipo tupla o array dai tipi dei parametri di un tipo di funzione costruttore T. Inferisce i tipi degli argomenti che devono essere passati al costruttore di una classe.

Esempio:


class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  greet() {
    return "Hello, " + this.greeting;
  }
}


type GreeterParams = ConstructorParameters<typeof Greeter>;

// GreeterParams è ora [message: string]

const paramsGreeter: GreeterParams = ["World"];
const greeterInstance = new Greeter(...paramsGreeter);

console.log(greeterInstance.greet()); // Outputs: Hello, World

Required

Required costruisce un tipo costituito da tutte le proprietà di T impostate su required. Rende tutte le proprietà opzionali richieste.

Esempio:


interface UserProfile {
  name: string;
  age?: number;
  email?: string;
}

type RequiredUserProfile = Required<UserProfile>;

// RequiredUserProfile è ora { name: string; age: number; email: string; }

const completeProfile: RequiredUserProfile = {
  name: "Alice",
  age: 30,
  email: "alice@example.com"
};

// const incompleteProfile: RequiredUserProfile = { name: "Bob" }; // Errore: La proprietà 'age' manca nel tipo '{ name: string; }' ma è richiesta nel tipo 'Required'.

Tipi Utility Avanzati

Tipi Letterali Template

I tipi letterali template ti consentono di costruire nuovi tipi letterali stringa concatenando tipi letterali stringa esistenti, tipi letterali numerici e altro ancora. Ciò consente una potente manipolazione dei tipi basata su stringhe.

Esempio:


type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/users` | `/api/products`;

type RequestURL = `${HTTPMethod} ${APIEndpoint}`;

// RequestURL è ora "GET /api/users" | "POST /api/users" | "PUT /api/users" | "DELETE /api/users" | "GET /api/products" | "POST /api/products" | "PUT /api/products" | "DELETE /api/products"

function makeRequest(url: RequestURL): void {
  console.log(`Making request to ${url}`);
}

makeRequest("GET /api/users"); // Valido
// makeRequest("INVALID /api/users"); // Errore

Tipi Condizionali

I tipi condizionali ti consentono di definire tipi che dipendono da una condizione espressa come relazione di tipo. Usano la parola chiave infer per estrarre informazioni sul tipo.

Esempio:


type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

// Se T è una Promise, allora il tipo è U; altrimenti, il tipo è T.

async function fetchData(): Promise<number> {
  return 42;
}


type Data = UnwrapPromise<ReturnType<typeof fetchData>>;

// Data è ora number

function processData(data: Data): void {
  console.log(data * 2);
}

processData(await fetchData());

Applicazioni Pratiche e Scenari Reali

Esploriamo scenari reali più complessi in cui i tipi utility brillano.

1. Gestione dei Form

Quando si ha a che fare con i moduli, spesso si hanno scenari in cui è necessario rappresentare i valori iniziali del modulo, i valori aggiornati del modulo e i valori finali inviati. I tipi utility possono aiutarti a gestire in modo efficiente questi diversi stati.


interface FormData {
  firstName: string;
  lastName: string;
  email: string;
  country: string; // Required
  city?: string; // Optional
  postalCode?: string;
  newsletterSubscription?: boolean;
}

// Initial form values (optional fields)
type InitialFormValues = Partial<FormData>;

// Updated form values (some fields might be missing)
type UpdatedFormValues = Partial<FormData>;

// Required fields for submission
type RequiredForSubmission = Required<Pick<FormData, 'firstName' | 'lastName' | 'email' | 'country'>>;

// Use these types in your form components
function initializeForm(initialValues: InitialFormValues): void { }
function updateForm(updates: UpdatedFormValues): void {}
function submitForm(data: RequiredForSubmission): void {}


const initialForm: InitialFormValues = { newsletterSubscription: true };

const updateFormValues: UpdatedFormValues = {
    firstName: "John",
    lastName: "Doe"
};

// const submissionData: RequiredForSubmission = { firstName: "test", lastName: "test", email: "test" }; // ERROR: Missing 'country' 
const submissionData: RequiredForSubmission = { firstName: "test", lastName: "test", email: "test", country: "USA" }; //OK


2. Trasformazione dei Dati API

Quando si consumano dati da un'API, potrebbe essere necessario trasformare i dati in un formato diverso per la propria applicazione. I tipi utility possono aiutarti a definire la struttura dei dati trasformati.


interface APIResponse {
  user_id: string;
  first_name: string;
  last_name: string;
  email_address: string;
  profile_picture_url: string;
  is_active: boolean;
}

// Transform the API response to a more readable format
type UserData = {
  id: string;
  fullName: string;
  email: string;
  avatar: string;
  active: boolean;
};

function transformApiResponse(response: APIResponse): UserData {
  return {
    id: response.user_id,
    fullName: `${response.first_name} ${response.last_name}`,
    email: response.email_address,
    avatar: response.profile_picture_url,
    active: response.is_active
  };
}

function fetchAndTransformData(url: string): Promise<UserData> {
    return fetch(url)
        .then(response => response.json())
        .then(data => transformApiResponse(data));
}


// You can even enforce the type by:

function saferTransformApiResponse(response: APIResponse): UserData {
    const {user_id, first_name, last_name, email_address, profile_picture_url, is_active} = response;
    const transformed: UserData = {
        id: user_id,
        fullName: `${first_name} ${last_name}`,
        email: email_address,
        avatar: profile_picture_url,
        active: is_active
    };

    return transformed;
}

3. Gestione degli Oggetti di Configurazione

Gli oggetti di configurazione sono comuni in molte applicazioni. I tipi utility possono aiutarti a definire la struttura dell'oggetto di configurazione e garantire che venga utilizzato correttamente.


interface AppSettings {
  theme: "light" | "dark";
  language: string;
  notificationsEnabled: boolean;
  apiUrl?: string; // Optional API URL for different environments
  timeout?: number;  //Optional
}

// Default settings
const defaultSettings: AppSettings = {
  theme: "light",
  language: "en",
  notificationsEnabled: true
};

// Function to merge user settings with default settings
function mergeSettings(userSettings: Partial<AppSettings>): AppSettings {
  return { ...defaultSettings, ...userSettings };
}

// Use the merged settings in your application
const mergedSettings = mergeSettings({ theme: "dark", apiUrl: "https://customapi.example.com" });
console.log(mergedSettings);

Suggerimenti per un Uso Efficace dei Tipi Utility

  • Inizia in modo semplice: Inizia con i tipi utility di base come Partial e Readonly prima di passare a quelli più complessi.
  • Usa nomi descrittivi: Assegna ai tuoi alias di tipo nomi significativi per migliorare la leggibilità.
  • Combina i tipi utility: Puoi combinare più tipi utility per ottenere trasformazioni di tipo complesse.
  • Sfrutta il supporto dell'editor: Sfrutta l'eccellente supporto dell'editor di TypeScript per esplorare gli effetti dei tipi utility.
  • Comprendi i concetti sottostanti: Una solida comprensione del sistema di tipi di TypeScript è essenziale per un uso efficace dei tipi utility.

Conclusione

I tipi utility di TypeScript sono strumenti potenti che possono migliorare significativamente la qualità e la manutenibilità del tuo codice. Comprendendo e applicando efficacemente questi tipi utility, puoi scrivere applicazioni più pulite, più type-safe e più robuste che soddisfano le esigenze di un panorama di sviluppo globale. Questa guida ha fornito una panoramica completa dei tipi utility comuni ed esempi pratici. Sperimenta con loro ed esplora il loro potenziale per migliorare i tuoi progetti TypeScript. Ricorda di dare la priorità alla leggibilità e alla chiarezza quando usi i tipi utility e cerca sempre di scrivere codice facile da capire e mantenere, indipendentemente da dove si trovino i tuoi colleghi sviluppatori.

Padroneggiare i Tipi Utility di TypeScript: Una Guida Pratica per Sviluppatori Globali | MLOG