TypeScript - Types Mappés : Guide du Débutant

Salut à toi, futurs magiciens de TypeScript ! Aujourd'hui, nous allons entreprendre un voyage passionnant à la découverte du monde des Types Mappés. Ne t'inquiète pas si tu es nouveau dans le monde de la programmation - je serai ton guide amical, et nous avancerons pas à pas. D'ici la fin de ce tutoriel, tu seras capable de mapper des types comme un pro !

TypeScript - Mapped Types

Qu'est-ce que les Types Mappés ?

Avant de plonger dedans, comprenons ce qu'are les Types Mappés. Imagine que tu as une boîte de chocolates mélangés et que tu veux créer une nouvelle boîte où chaque chocolate est enveloppé dans du papier doré. C'est essentiellement ce que font les Types Mappés dans TypeScript - ils prennent un type existant et le transforment en un nouveau type basé sur un ensemble de règles.

Types Mappés Prédéfinis

TypeScript vient avec quelques Types Mappés prédéfinis qui sont super utiles. Jetons un œil à chacun d'eux :

1. Partial<T>

Le type Partial<T> rend toutes les propriétés de T optionnelles. C'est comme passer d'une recette stricte à une recette flexible où tu peux sauter certains ingrédients.

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

type FlexibleRecipe = Partial<Recipe>;

// Maintenant, nous pouvons créer une recette sans toutes les propriétés
const quickSnack: FlexibleRecipe = {
name: "Toast",
// Nous pouvons sauter ingredients et cookingTime
};

Dans cet exemple, FlexibleRecipe nous permet de créer une recette sans spécifier toutes les propriétés. C'est parfait lorsque tu veux mettre à jour seulement une partie d'un objet.

2. Required<T>

Required<T> est l'opposé de Partial<T>. Il rend toutes les propriétés obligatoires, même si elles étaient optionnelles dans le type original.

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

type CompleteUserProfile = Required<UserProfile>;

// Maintenant, nous devons fournir toutes les propriétés
const user: CompleteUserProfile = {
name: "Alice",
age: 30,
email: "[email protected]"
};

Ici, CompleteUserProfile nous assure que nous fournissons toutes les propriétés, y compris age et email qui étaient optionnels dans le UserProfile original.

3. Readonly<T>

Readonly<T> rend toutes les propriétés de T en lecture seule. C'est comme mettre ton type dans une vitrine - tu peux regarder, mais tu ne peux pas toucher !

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

type CollectibleToy = Readonly<Toy>;

const actionFigure: CollectibleToy = {
name: "Superhéros",
price: 19.99
};

// Cela provoquera une erreur
// actionFigure.price = 29.99;

Dans cet exemple, une fois que nous avons créé notre actionFigure, nous ne pouvons pas modifier ses propriétés. C'est génial pour créer des objets immuables.

4. Pick<T, K>

Pick<T, K> crée un nouveau type en sélectionnant seulement les propriétés spécifiées K de T. C'est comme choisir tes fonctionnalités favorites d'un type.

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"
};

Ici, BasicPhoneInfo inclut seulement les propriétés brand et model de Smartphone, laissant les autres de côté.

5. Omit<T, K>

Omit<T, K> est l'opposé de Pick<T, K>. Il crée un nouveau type en supprimant les propriétés spécifiées K de T.

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

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

const preview: BookPreview = {
title: "Aventures TypeScript",
author: "Magicien du Code"
};

Dans ce cas, BookPreview inclut toutes les propriétés de Book sauf pages et isbn.

Exemples d'utilisation des Types Mappés

Maintenant que nous avons vu les Types Mappés prédéfinis, examinons quelques exemples pratiques de leur utilisation dans des scénarios réels.

Exemple 1 : Création d'un État de Formulaire

Imagine que tu construis un formulaire et que tu veux suivre les champs qui ont été modifiés :

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

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

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

Ici, nous avons créé un type FormTouched qui a les mêmes clés que LoginForm, mais toutes les valeurs sont des booléens indiquant si le champ a été touché.

Exemple 2 : Enveloppe de Réponse API

Disons que tu as une API qui renvoie différents types de données, et que tu veux envelopper chaque réponse dans un format 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()
};

Cet exemple montre comment nous pouvons utiliser des génériques avec les Types Mappés pour créer des structures de type flexibles et réutilisables.

Création de Types Mappés Personnalisés

Maintenant, mettons nos muscles TypeScript à l'épreuve et créons quelques Types Mappés personnalisés !

Type Personnalisé 1 : Nullable

Créons un type qui rend toutes les propriétés 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  // Cela est maintenant valide
};

Notre type Nullable<T> permet à toute propriété d'être soit son type original, soit null.

Type Personnalisé 2 : Freezable

Créons un type qui ajoute une méthode freeze à un objet :

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";  // Cela provoquera une erreur

Ce type personnalisé ajoute une méthode freeze à n'importe quel objet, nous permettant de créer une version immuable de celui-ci.

Conclusion

Wahou, nous avons couvert beaucoup de terrain aujourd'hui ! Des Types Mappés prédéfinis à la création de nos propres types personnalisés, tu as vu à quel point TypeScript peut être puissant et flexible. Les Types Mappés sont comme des baguettes magiques dans ton toolkit TypeScript - ils te permettent de transformer et de manipuler des types de manière incroyablement utile.

Souviens-toi, la clé pour maîtriser les Types Mappés est la pratique. Essaie de créer les tiens, expérimente avec différentes combinaisons, et bientôt tu écriras du code TypeScript qui n'est pas seulement fonctionnel, mais élégant et sûr.

Continue de coder, continue d'apprendre, et surtout, amuse-toi avec TypeScript !

Credits: Image by storyset