Pourquoi Generics en TypeScript ?
Le generics est un élément essentiel de la programmation moderne, offrant une flexibilité accrue dans la conception et l'utilisation des types. En tant que développeur senior TypeScript avec 10+ ans d'expérience, vous devriez comprendre qu'un bon usage des generics peut grandement améliorer la lisibilité du code, les performances et la robustesse de vos applications.
Dans le monde réel, imaginez un scénario où vous travaillez sur une application qui doit gérer différents types de données. Sans generics, vous devriez peut-être écrire des fonctions pour chaque type de données distincte, ce qui rend votre code plus complexe et difficile à maintenir. Avec les generics, vous pouvez créer des composants réutilisables qui peuvent travailler avec n'importe quel type de données.
Prerequis
- Connaissances en TypeScript (version 2.3 ou supérieure recommandée)
- Un environnement de développement Node.js
- L'IDE de votre choix (VSCode, WebStorm, etc.)
Installez les outils suivants :
npm install -g typescript
tsc --init
Concepts fondamentaux
1. Définition des Generics
Les generics permettent de créer des composants qui peuvent travailler avec un type non spécifié. Ils sont déclarés entre < >.
function identity<T>(arg: T): T {
return arg;
}
2. Utilisation d'un Generic en Interface
Vous pouvez également utiliser des generics dans les interfaces pour créer des types flexibles.
interface Box<T> {
value: T;
}
const numberBox: Box<number> = { value: 10 };
const stringBox: Box<string> = { value: "Hello" };
3. Generics avec Classes
Les classes peuvent également utiliser des generics pour offrir une flexibilité accrue.
class GenericArray<T> {
private items: T[] = [];
add(item: T) {
this.items.push(item);
}
get(index: number): T {
return this.items[index];
}
}
const numbers = new GenericArray<number>();
numbers.add(10);
numbers.add(20);
console.log(numbers.get(0)); // Output: 10
4. Generics avec Fonctions de Callbacks
Les generics peuvent être utilisés dans les fonctions qui acceptent des callbacks.
function map<T, U>(array: T[], callback: (item: T) => U): U[] {
const result: U[] = [];
for (const item of array) {
result.push(callback(item));
}
return result;
}
const numbers = [1, 2, 3];
const doubledNumbers = map(numbers, (num) => num * 2);
console.log(doubledNumbers); // Output: [2, 4, 6]
Mise en pratique : Projet fil rouge
Nous allons construire un gestionnaire de tâches simple pour illustrer l'utilisation des generics.
Étape 1 : Création du projet
Créez un nouveau dossier et initialisez le projet :
mkdir task-manager
cd task-manager
npm init -y
Installez les dépendances nécessaires :
npm install typescript @types/node --save-dev
npx tsc --init
Étape 2 : Configuration du fichier tsconfig.json
Modifiez le fichier tsconfig.json pour configurer la compilation TypeScript :
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist"
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
Étape 3 : Création des fichiers
Créez un dossier src et ajoutez les fichiers suivants :
task.tstask-manager.tsindex.ts
task.ts
// task.ts
export interface Task {
id: number;
title: string;
completed: boolean;
}
task-manager.ts
// task-manager.ts
import { Task } from './task';
export class TaskManager<T extends Task> {
private tasks: T[] = [];
addTask(task: T) {
this.tasks.push(task);
}
getTasks(): T[] {
return this.tasks;
}
}
index.ts
// index.ts
import { Task, TaskManager } from './task-manager';
const task1: Task = { id: 1, title: 'Task 1', completed: false };
const task2: Task = { id: 2, title: 'Task 2', completed: true };
const taskManager = new TaskManager<Task>();
taskManager.addTask(task1);
taskManager.addTask(task2);
console.log(taskManager.getTasks());
Compilation et exécution
Compiler le code TypeScript :
npx tsc
Exécuter le fichier compilé :
node dist/index.js
Erreurs fréquentes et debugging
1. Erreur de type non spécifié
function printArray(array) {
array.forEach(item => console.log(item));
}
const numbers = [1, 2, 3];
printArray(numbers); // Pas d'erreur compile-time
Correction :
function printArray<T>(array: T[]) {
array.forEach(item => console.log(item));
}
const numbers = [1, 2, 3];
printArray<number>(numbers);
2. Erreur d'incompatibilité de type
interface Box<T> {
value: T;
}
function swap(a: Box<any>, b: Box<any>) {
const temp = a.value;
a.value = b.value;
b.value = temp;
}
const box1 = { value: 10 };
const box2 = { value: "Hello" };
swap(box1, box2); // Pas d'erreur compile-time
Correction :
interface Box<T> {
value: T;
}
function swap<T>(a: Box<T>, b: Box<T>) {
const temp = a.value;
a.value = b.value;
b.value = temp;
}
const box1: Box<number> = { value: 10 };
const box2: Box<string> = { value: "Hello" };
swap(box1, box2); // Erreur compile-time
3. Erreur de type non spécifique
function findFirst<T>(array: T[], predicate: (item: T) => boolean): T {
for (const item of array) {
if (predicate(item)) {
return item;
}
}
}
const numbers = [1, 2, 3];
const firstEven = findFirst(numbers, num => num % 2 === 0);
console.log(firstEven); // Pas d'erreur compile-time
Correction :
function findFirst<T>(array: T[], predicate: (item: T) => boolean): T | undefined {
for (const item of array) {
if (predicate(item)) {
return item;
}
}
}
const numbers = [1, 2, 3];
const firstEven = findFirst(numbers, num => num % 2 === 0);
console.log(firstEven); // Output: undefined
Pour aller plus loin
1. Les generics avec des tuples
Les tuples sont des structures de données qui permettent de stocker un nombre fixe d'éléments de types différents. Vous pouvez utiliser des generics pour créer des fonctions qui travaillent sur des tuples.
function swapTuple<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
const tuple = [1, "Hello"];
const swappedTuple = swapTuple(tuple);
console.log(swappedTuple); // Output: ["Hello", 1]
2. Les generics avec des classes génériques
Les classes génériques peuvent être utilisées pour créer des structures de données réutilisables qui peuvent travailler avec n'importe quel type de données.
class PriorityQueue<T> {
private items: { priority: number, item: T }[] = [];
enqueue(item: T, priority: number) {
this.items.push({ item, priority });
this.items.sort((a, b) => a.priority - b.priority);
}
dequeue(): T | undefined {
if (this.items.length > 0) {
return this.items.shift()?.item;
}
return undefined;
}
}
const pq = new PriorityQueue<number>();
pq.enqueue(1, 2);
pq.enqueue(3, 1);
console.log(pq.dequeue()); // Output: 3
3. Les generics avec des interfaces génériques
Les interfaces génériques peuvent être utilisées pour définir des types flexibles qui peuvent travailler avec n'importe quel type de données.
interface Cache<T> {
set(key: string, value: T): void;
get(key: string): T | undefined;
}
class InMemoryCache<T> implements Cache<T> {
private store: { [key: string]: T } = {};
set(key: string, value: T) {
this.store[key] = value;
}
get(key: string): T | undefined {
return this.store[key];
}
}
const cache = new InMemoryCache<number>();
cache.set("one", 1);
cache.set("two", 2);
console.log(cache.get("one")); // Output: 1
Défi pratique : Créer une API de blog
Créez une API simple en utilisant Express.js et TypeScript pour gérer les articles d'un blog. Utilisez des generics pour créer un système générique de gestion des ressources.
// server.ts
import express from 'express';
import { TaskManager } from './task-manager';
const app = express();
app.use(express.json());
interface Article {
id: number;
title: string;
content: string;
}
const articleManager = new TaskManager<Article>();
app.post('/articles', (req, res) => {
const article: Article = req.body;
article.id = Date.now();
articleManager.addTask(article);
res.status(201).json(article);
});
app.get('/articles', (req, res) => {
const articles = articleManager.getTasks();
res.json(articles);
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Installation des dépendances
npm install express --save
Compilation et exécution
npx tsc
node dist/server.js
Ce tutoriel vous a permis de comprendre les bases et les concepts avancés des generics en TypeScript. Vous avez également créé un projet fil rouge pour mettre en pratique vos connaissances. Continuez à explorer les autres fonctionnalités de TypeScript pour approfondir votre compréhension du langage.