TypeScript - Tipi Mappati: Una Guida per Principianti

Ciao là, futuri maghi di TypeScript! Oggi ci imbarcheremo in un viaggio emozionante nel mondo dei Tipi Mappati. Non preoccupatevi se siete nuovi alla programmazione - sarò il vostro guida amichevole, e procederemo passo per passo. Alla fine di questo tutorial, mapperete i tipi come un professionista!

TypeScript - Mapped Types

Cos'è un Tipo Mappato?

Prima di immergerci, capiamo cos'è un Tipo Mappato. Immagina di avere una scatola di cioccolatini misti e di voler creare una nuova scatola dove ogni cioccolatino è avvolto in foglia d'oro. Questo è essenzialmente ciò che fanno i Tipi Mappati in TypeScript - prendono un tipo esistente e lo trasformano in un nuovo tipo basato su un insieme di regole.

Tipi Mappati Predefiniti

TypeScript viene con alcuni Tipi Mappati predefiniti che sono super utili. Esaminiamoli uno per uno:

1. Partial<T>

Il tipo Partial<T> rende tutte le proprietà di T opzionali. È come trasformare una ricetta rigorosa in una flessibile dove puoi saltare alcuni ingredienti.

interface Recipe {
name: string;
ingredients: string[];
cookingTime: number;
}

type FlexibleRecipe = Partial<Recipe>;

// Ora possiamo creare una ricetta senza tutte le proprietà
const quickSnack: FlexibleRecipe = {
name: "Toast",
// Possiamo saltare ingredients e cookingTime
};

In questo esempio, FlexibleRecipe ci permette di creare una ricetta senza specificare tutte le proprietà. È perfetto quando vuoi aggiornare solo una parte di un oggetto.

2. Required<T>

Required<T> è l'opposto di Partial<T>. Rende tutte le proprietà obbligatorie, anche se erano opzionali nel tipo originale.

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

type CompleteUserProfile = Required<UserProfile>;

// Ora dobbiamo fornire tutte le proprietà
const user: CompleteUserProfile = {
name: "Alice",
age: 30,
email: "[email protected]"
};

Qui, CompleteUserProfile assicura che forniamo tutte le proprietà, inclusi age e email che erano opzionali nel UserProfile originale.

3. Readonly<T>

Readonly<T> rende tutte le proprietà di T di sola lettura. È come mettere il tuo tipo in una vetrina - puoi guardare, ma non puoi toccare!

interface Toy {
name: string;
price: number;
}

type CollectibleToy = Readonly<Toy>;

const actionFigure: CollectibleToy = {
name: "Supereroe",
price: 19.99
};

// Questo causerà un errore
// actionFigure.price = 29.99;

In questo esempio, una volta creato il nostro actionFigure, non possiamo modificare le sue proprietà. È ottimo per creare oggetti immutabili.

4. Pick<T, K>

Pick<T, K> crea un nuovo tipo selezionando solo le proprietà specificate K da T. È come scegliere i tuoi feature preferiti da un tipo.

interface Smartphone {
brand: string;
model: string;
year: number;
color: string;
price: number;
}

type BasicPhoneInfo = Pick<Smartphone, 'brand' | 'model'>;

const myPhone: BasicPhoneInfo = {
brand: "TechBrand",
model: "X2000"
};

Qui, BasicPhoneInfo include solo le proprietà brand e model da Smartphone, lasciando fuori le altre.

5. Omit<T, K>

Omit<T, K> è l'opposto di Pick<T, K>. Crea un nuovo tipo rimuovendo le proprietà specificate K da T.

interface Book {
title: string;
author: string;
pages: number;
isbn: string;
}

type BookPreview = Omit<Book, 'pages' | 'isbn'>;

const preview: BookPreview = {
title: "Avventure TypeScript",
author: "Mago del Codice"
};

In questo caso, BookPreview include tutte le proprietà di Book tranne pages e isbn.

Esempi di Utilizzo dei Tipi Mappati

Ora che abbiamo visto i Tipi Mappati predefiniti, esaminiamo alcuni esempi pratici di come possiamo usarli in scenari reali.

Esempio 1: Creazione di uno Stato di Modulo

Immagina di essere in procinto di costruire un modulo e di voler tenere traccia di quali campi sono stati modificati:

interface LoginForm {
username: string;
password: string;
rememberMe: boolean;
}

type FormTouched = { [K in keyof LoginForm]: boolean };

const touchedFields: FormTouched = {
username: true,
password: false,
rememberMe: true
};

Qui, abbiamo creato un tipo FormTouched che ha le stesse chiavi di LoginForm, ma tutti i valori sono booleani che indicano se il campo è stato toccato.

Esempio 2: Wrapper di Risposta API

Supponiamo di avere un'API che restituisce diversi tipi di dati e di voler incapsulare ogni risposta in un formato standard:

interface ApiResponse<T> {
data: T;
status: 'success' | 'error';
timestamp: number;
}

type UserData = { id: number; name: string; email: string };
type ProductData = { id: number; name: string; price: number };

const userResponse: ApiResponse<UserData> = {
data: { id: 1, name: "John Doe", email: "[email protected]" },
status: 'success',
timestamp: Date.now()
};

const productResponse: ApiResponse<ProductData> = {
data: { id: 101, name: "Laptop", price: 999.99 },
status: 'success',
timestamp: Date.now()
};

Questo esempio mostra come possiamo usare i generici con i Tipi Mappati per creare strutture di tipo flessibili e riutilizzabili.

Creazione di Tipi Mappati Personalizzati

Ora, mettiamo in pratica le nostre abilità di TypeScript e creiamo alcuni Tipi Mappati personalizzati!

Tipo Personalizzato 1: Nullable

Creiamo un tipo che rende tutte le proprietà nullable:

type Nullable<T> = { [K in keyof T]: T[K] | null };

interface Person {
name: string;
age: number;
}

type NullablePerson = Nullable<Person>;

const maybePerson: NullablePerson = {
name: "Jane",
age: null  // Questo è ora valido
};

Il nostro tipo Nullable<T> permette a qualsiasi proprietà di essere il suo tipo originale o null.

Tipo Personalizzato 2: Freezable

Creiamo un tipo che aggiunge un metodo freeze a un oggetto:

type Freezable<T> = T & { freeze(): Readonly<T> };

interface Config {
theme: string;
fontSize: number;
}

function makeFreezable<T>(obj: T): Freezable<T> {
return {
...obj,
freeze() {
return Object.freeze({ ...this }) as Readonly<T>;
}
};
}

const config = makeFreezable<Config>({
theme: "dark",
fontSize: 14
});

const frozenConfig = config.freeze();
// frozenConfig.theme = "light";  // Questo causerebbe un errore

Questo tipo personalizzato aggiunge un metodo freeze a qualsiasi oggetto, permettendoci di creare una versione immutabile di esso.

Conclusione

Wow, abbiamo coperto molto terreno oggi! Dai Tipi Mappati predefiniti alla creazione dei nostri tipi personalizzati, avete visto quanto TypeScript possa essere potente e flessibile. I Tipi Mappati sono come bacchette magiche nel vostro toolkit TypeScript - vi permettono di trasformare e manipolare i tipi in modi incredibilmente utili.

Ricordate, la chiave per padroneggiare i Tipi Mappati è la pratica. Prova a crearne alcuni, esperimenta con diverse combinazioni, e presto scriverete codice TypeScript che non è solo funzionale, ma anche elegante e sicuro.

Continuate a codificare, continuate a imparare, e, soprattutto, divertitevi con TypeScript!

Credits: Image by storyset