Français

Libérez la puissance des types utilitaires TypeScript pour écrire du code plus propre, plus maintenable et plus sûr en termes de types. Explorez des applications pratiques avec des exemples concrets.

Maîtriser les types utilitaires TypeScript : un guide pratique pour les développeurs mondiaux

TypeScript offre un ensemble puissant de types utilitaires intégrés qui peuvent améliorer considérablement la sécurité des types, la lisibilité et la maintenabilité de votre code. Ces types utilitaires sont essentiellement des transformations de types prédéfinies que vous pouvez appliquer aux types existants, vous évitant ainsi d'écrire du code répétitif et sujet aux erreurs. Ce guide explorera divers types utilitaires avec des exemples pratiques qui résonnent auprès des développeurs du monde entier.

Pourquoi utiliser les types utilitaires ?

Les types utilitaires répondent aux scénarios courants de manipulation de types. En les utilisant, vous pouvez :

Types utilitaires principaux

Partial<T>

Partial<T> construit un type où toutes les propriétés de T sont définies comme optionnelles. C'est particulièrement utile lorsque vous souhaitez créer un type pour des mises à jour partielles ou des objets de configuration.

Exemple :

Imaginez que vous construisez une plateforme e-commerce avec des clients de diverses régions. Vous avez un type 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;
  }
}

Lors de la mise à jour des informations d'un client, vous ne voudrez peut-être pas exiger tous les champs. Partial<Customer> vous permet de définir un type où toutes les propriétés de Customer sont optionnelles :


type PartialCustomer = Partial<Customer>;

function updateCustomer(id: string, updates: PartialCustomer): void {
  // ... implémentation pour mettre à jour le client avec l'ID donné
}

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

Readonly<T>

Readonly<T> construit un type où toutes les propriétés de T sont définies comme readonly, empêchant la modification après initialisation. Ceci est précieux pour assurer l'immuabilité.

Exemple :

Considérez un objet de configuration pour votre application globale :


interface AppConfig {
  apiUrl: string;
  theme: string;
  supportedLanguages: string[];
  version: string; // version ajoutée
}

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

Pour empêcher la modification accidentelle de la configuration après initialisation, vous pouvez utiliser Readonly<AppConfig> :


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"; // Erreur : Impossible d'assigner à 'apiUrl' car c'est une propriété en lecture seule.

Pick<T, K>

Pick<T, K> construit un type en sélectionnant l'ensemble des propriétés K de T, où K est une union de types littéraux de chaîne représentant les noms de propriétés que vous souhaitez inclure.

Exemple :

Supposons que vous ayez une interface Event avec diverses propriétés :


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

Si vous n'avez besoin que du title, de la location et de la startTime pour un composant d'affichage spécifique, vous pouvez utiliser Pick :


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

function displayEventSummary(event: EventSummary): void {
  console.log(`Événement : ${event.title} à ${event.location} le ${event.startTime}`);
}

Omit<T, K>

Omit<T, K> construit un type en excluant l'ensemble des propriétés K de T, où K est une union de types littéraux de chaîne représentant les noms de propriétés que vous souhaitez exclure. C'est l'inverse de Pick.

Exemple :

En utilisant la même interface Event, si vous souhaitez créer un type pour la création de nouveaux événements, vous pourriez vouloir exclure la propriété id, qui est généralement générée par le backend :


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

function createEvent(event: NewEvent): void {
  // ... implémentation pour créer un nouvel événement
}

Record<K, T>

Record<K, T> construit un type d'objet dont les clés de propriété sont K et dont les valeurs de propriété sont T. K peut être une union de types littéraux de chaîne, de types littéraux de nombre ou d'un symbole. C'est parfait pour créer des dictionnaires ou des cartes.

Exemple :

Imaginez que vous ayez besoin de stocker des traductions pour l'interface utilisateur de votre application. Vous pouvez utiliser Record pour définir un type pour vos traductions :


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; // Simplifié
  return translations[key] || key; // Retour à la clé si aucune traduction n'est trouvée
}

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

Exclude<T, U>

Exclude<T, U> construit un type en excluant de T tous les membres d'union qui sont assignables à U. Il est utile pour filtrer des types spécifiques d'une union.

Exemple :

Vous pourriez avoir un type représentant différents types d'événements :


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

Si vous souhaitez créer un type qui exclut les événements de type "webinar", vous pouvez utiliser Exclude :


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

// PhysicalEvent est maintenant "concert" | "conference" | "workshop"

function attendPhysicalEvent(event: PhysicalEvent): void {
  console.log(`Participation à un ${event}`);
}

// attendPhysicalEvent("webinar"); // Erreur : L'argument de type '"webinar"' n'est pas assignable au paramètre de type '"concert" | "conference" | "workshop"'.

attendPhysicalEvent("concert"); // Valide

Extract<T, U>

Extract<T, U> construit un type en extrayant de T tous les membres d'union qui sont assignables à U. C'est l'inverse de Exclude.

Exemple :

En utilisant le même EventType, vous pouvez extraire le type d'événement webinar :


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

// OnlineEvent est maintenant "webinar"

function attendOnlineEvent(event: OnlineEvent): void {
  console.log(`Participation à un ${event} en ligne`);
}

attendOnlineEvent("webinar"); // Valide
// attendOnlineEvent("concert"); // Erreur : L'argument de type '"concert"' n'est pas assignable au paramètre de type '"webinar"'.

NonNullable<T>

NonNullable<T> construit un type en excluant null et undefined de T.

Exemple :


type MaybeString = string | null | undefined;

type DefinitelyString = NonNullable<MaybeString>;

// DefinitelyString est maintenant string

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

// processString(null); // Erreur : L'argument de type 'null' n'est pas assignable au paramètre de type 'string'.
// processString(undefined); // Erreur : L'argument de type 'undefined' n'est pas assignable au paramètre de type 'string'.
processString("hello"); // Valide

ReturnType<T>

ReturnType<T> construit un type composé du type de retour de la fonction T.

Exemple :


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

type Greeting = ReturnType<typeof greet>;

// Greeting est maintenant string

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

console.log(message);

Parameters<T>

Parameters<T> construit un type de tuple à partir des types des paramètres d'un type de fonction T.

Exemple :


function logEvent(eventName: string, eventData: object): void {
  console.log(`Événement : ${eventName}`, eventData);
}

type LogEventParams = Parameters<typeof logEvent>;

// LogEventParams est maintenant [eventName: string, eventData: object]

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

logEvent(...params);

ConstructorParameters<T>

ConstructorParameters<T> construit un type de tuple ou de tableau à partir des types des paramètres d'un type de fonction constructeur T. Il infère les types des arguments qui doivent être passés au constructeur d'une classe.

Exemple :


class Greeter {
  greeting: string;

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

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


type GreeterParams = ConstructorParameters<typeof Greeter>;

// GreeterParams est maintenant [message: string]

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

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

Required<T>

Required<T> construit un type composé de toutes les propriétés de T définies comme requises. Il rend toutes les propriétés optionnelles obligatoires.

Exemple :


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

type RequiredUserProfile = Required<UserProfile>;

// RequiredUserProfile est maintenant { name: string; age: number; email: string; }

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

// const incompleteProfile: RequiredUserProfile = { name: "Bob" }; // Erreur : La propriété 'age' est manquante dans le type '{ name: string; }' mais est requise dans le type 'Required'.

Types utilitaires avancés

Types de littéraux de modèle

Les types de littéraux de modèle vous permettent de construire de nouveaux types de littéraux de chaîne en concaténant des types de littéraux de chaîne, des types de littéraux de nombre, etc. existants. Cela permet une manipulation puissante des types basés sur des chaînes.

Exemple :


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

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

// RequestURL est maintenant "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(`Faire une requête vers ${url}`);
}

makeRequest("GET /api/users"); // Valide
// makeRequest("INVALID /api/users"); // Erreur

Types conditionnels

Les types conditionnels vous permettent de définir des types qui dépendent d'une condition exprimée comme une relation de type. Ils utilisent le mot-clé infer pour extraire des informations de type.

Exemple :


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

// Si T est une Promise<U>, alors le type est U ; sinon, le type est T.

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


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

// Data est maintenant number

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

processData(await fetchData());

Applications pratiques et scénarios réels

Explorons des scénarios réels plus complexes où les types utilitaires excellent.

1. Gestion des formulaires

Lors du traitement des formulaires, vous avez souvent des scénarios où vous devez représenter les valeurs initiales du formulaire, les valeurs mises à jour du formulaire et les valeurs soumises finales. Les types utilitaires peuvent vous aider à gérer ces différents états efficacement.


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

// Valeurs initiales du formulaire (champs optionnels)
type InitialFormValues = Partial<FormData>;

// Valeurs du formulaire mises à jour (certains champs peuvent être manquants)
type UpdatedFormValues = Partial<FormData>;

// Champs requis pour la soumission
type RequiredForSubmission = Required<Pick<FormData, 'firstName' | 'lastName' | 'email' | 'country'>>;

// Utilisez ces types dans vos composants de formulaire
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" }; // ERREUR : 'country' manquant 
const submissionData: RequiredForSubmission = { firstName: "test", lastName: "test", email: "test", country: "USA" }; // OK


2. Transformation des données d'API

Lors de la consommation de données d'une API, vous devrez peut-être transformer les données dans un format différent pour votre application. Les types utilitaires peuvent vous aider à définir la structure des données transformées.


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

// Transformer la réponse de l'API dans un format plus lisible
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));
}


// Vous pouvez même imposer le type en :

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. Gestion des objets de configuration

Les objets de configuration sont courants dans de nombreuses applications. Les types utilitaires peuvent vous aider à définir la structure de l'objet de configuration et à garantir qu'il est utilisé correctement.


interface AppSettings {
  theme: "light" | "dark";
  language: string;
  notificationsEnabled: boolean;
  apiUrl?: string; // URL d'API optionnelle pour différents environnements
  timeout?: number;  //Optionnel
}

// Paramètres par défaut
const defaultSettings: AppSettings = {
  theme: "light",
  language: "en",
  notificationsEnabled: true
};

// Fonction pour fusionner les paramètres utilisateur avec les paramètres par défaut
function mergeSettings(userSettings: Partial<AppSettings>): AppSettings {
  return { ...defaultSettings, ...userSettings };
}

// Utiliser les paramètres fusionnés dans votre application
const mergedSettings = mergeSettings({ theme: "dark", apiUrl: "https://customapi.example.com" });
console.log(mergedSettings);

Conseils pour une utilisation efficace des types utilitaires

Conclusion

Les types utilitaires TypeScript sont des outils puissants qui peuvent améliorer considérablement la qualité et la maintenabilité de votre code. En comprenant et en appliquant efficacement ces types utilitaires, vous pouvez écrire des applications plus propres, plus sûres en termes de types et plus robustes qui répondent aux exigences d'un paysage de développement mondial. Ce guide a fourni un aperçu complet des types utilitaires courants et des exemples pratiques. Expérimentez-les et explorez leur potentiel pour améliorer vos projets TypeScript. N'oubliez pas de privilégier la lisibilité et la clarté lors de l'utilisation des types utilitaires, et efforcez-vous toujours d'écrire du code facile à comprendre et à maintenir, où que se trouvent vos collègues développeurs.