Techniques de métaprogrammation TypeScript expliquées
La métaprogrammation est une technique puissante qui permet aux programmes de se manipuler eux-mêmes ou de manipuler d'autres programmes. En TypeScript, la métaprogrammation fait référence à la capacité d'utiliser des types, des génériques et des décorateurs pour améliorer la flexibilité et l'abstraction du code. Cet article explore les principales techniques de métaprogrammation dans TypeScript et comment les mettre en œuvre efficacement.
1. Utilisation de génériques pour un code flexible
Les génériques permettent aux fonctions et aux classes de fonctionner avec une variété de types, augmentant ainsi la flexibilité et la réutilisabilité du code. En introduisant des paramètres de type, nous pouvons rendre notre code générique tout en préservant la sécurité des types.
function identity<T>(arg: T): T {
return arg;
}
const num = identity<number>(42);
const str = identity<string>("Hello");
Dans cet exemple, <T>
permet à la fonction identity
d'accepter n'importe quel type et de renvoyer le même type, garantissant ainsi flexibilité et sécurité de type.
2. Inférence de type et types conditionnels
Le système d'inférence de types de TypeScript déduit automatiquement les types d'expressions. De plus, les types conditionnels permettent de créer des types qui dépendent de conditions, ce qui permet des techniques de métaprogrammation plus avancées.
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
Dans cet exemple, IsString
est un type conditionnel qui vérifie si un type donné T
étend string
. Il renvoie true
pour les chaînes et false
pour les autres types.
3. Types mappés
Les types mappés sont un moyen de transformer un type en un autre en parcourant les propriétés d'un type. Cela est particulièrement utile en métaprogrammation pour créer des variantes de types existants.
type ReadOnly<T> = {
readonly [K in keyof T]: T[K];
};
interface User {
name: string;
age: number;
}
const user: ReadOnly<User> = {
name: "John",
age: 30,
};
// user.name = "Doe"; // Error: Cannot assign to 'name' because it is a read-only property.
Ici, ReadOnly
est un type mappé qui rend toutes les propriétés d'un type donné readonly
. Cela garantit que les objets de ce type ne peuvent pas voir leurs propriétés modifiées.
4. Types de littéraux de modèles
TypeScript vous permet de manipuler des types de chaînes avec des littéraux de modèle. Cette fonctionnalité permet la métaprogrammation pour les opérations basées sur des chaînes.
type WelcomeMessage<T extends string> = `Welcome, ${T}!`;
type Message = WelcomeMessage<"Alice">; // "Welcome, Alice!"
Cette technique peut être utile pour générer des types de chaînes de manière dynamique, ce qui est courant dans les grandes applications qui reposent sur des modèles de chaînes cohérents.
5. Définitions de type récursif
TypeScript autorise les types récursifs, qui sont des types qui se réfèrent à eux-mêmes. Cela est particulièrement utile pour la métaprogrammation lorsqu'il s'agit de structures de données complexes comme des objets JSON ou des données profondément imbriquées.
type Json = string | number | boolean | null | { [key: string]: Json } | Json[];
const data: Json = {
name: "John",
age: 30,
friends: ["Alice", "Bob"],
};
Dans cet exemple, Json
est un type récursif qui peut représenter n'importe quelle structure de données JSON valide, permettant des représentations de données flexibles.
6. Décorateurs pour la métaprogrammation
Les décorateurs dans TypeScript sont une forme de métaprogrammation utilisée pour modifier ou annoter des classes et des méthodes. Ils nous permettent d'appliquer un comportement de manière dynamique, ce qui les rend idéaux pour la journalisation, la validation ou l'injection de dépendances.
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with`, args);
return originalMethod.apply(this, args);
};
}
class Calculator {
@Log
add(a: number, b: number): number {
return a + b;
}
}
const calc = new Calculator();
calc.add(2, 3); // Logs: "Calling add with [2, 3]"
Dans cet exemple, le décorateur Log
enregistre le nom et les arguments de la méthode à chaque fois que la méthode add
est appelée. Il s'agit d'un moyen puissant d'étendre ou de modifier le comportement sans modifier directement le code de la méthode.
Conclusion
Les capacités de métaprogrammation de TypeScript permettent aux développeurs d'écrire du code flexible, réutilisable et évolutif. Des techniques telles que les génériques, les types conditionnels, les décorateurs et les types littéraux de modèles ouvrent de nouvelles possibilités pour créer des applications robustes et maintenables. En maîtrisant ces fonctionnalités avancées, vous pouvez exploiter tout le potentiel de TypeScript dans vos projets.