Maison > Article > interface Web > Principaux concepts de dactylographie avancés que tout développeur devrait connaître
TypeScript est un langage de programmation moderne souvent préféré à JavaScript pour sa sécurité de type supplémentaire. Dans cet article, je partagerai les 10 principaux concepts TypeScript qui vous aideront à perfectionner vos compétences en programmation TypeScript. Êtes-vous prêt ?C'est parti.
1.Génériques : En utilisant des génériques, nous pouvons créer des types réutilisables, qui seront utiles pour traiter les données d'aujourd'hui ainsi que les données de demain.
Exemple de génériques :
Nous pourrions vouloir une fonction dans Typescript qui prend un argument comme un certain type, et nous pourrions vouloir renvoyer le même type.
function func<T>(args:T):T{ return args; }
2.Génériques avec contraintes de type : Limitons maintenant le type T en le définissant pour n'accepter que des chaînes et des entiers :
function func<T extends string | number>(value: T): T { return value; } const stringValue = func("Hello"); // Works, T is string const numberValue = func(42); // Works, T is number // const booleanValue = func(true); // Error: Type 'boolean' is not assignable to type 'string | number'
3.Interfaces génériques :
Les génériques d'interface sont utiles lorsque vous souhaitez définir des contrats (formes) pour des objets, des classes ou des fonctions qui fonctionnent avec différents types. Ils permettent de définir un blueprint pouvant s'adapter à différents types de données tout en gardant la structure cohérente.
// Generic interface with type parameters T and U interface Repository<T, U> { items: T[]; // Array of items of type T add(item: T): void; // Function to add an item of type T getById(id: U): T | undefined; // Function to get an item by ID of type U } // Implementing the Repository interface for a User entity interface User { id: number; name: string; } class UserRepository implements Repository<User, number> { items: User[] = []; add(item: User): void { this.items.push(item); } getById(idOrName: number | string): User | undefined { if (typeof idOrName === 'string') { // Search by name if idOrName is a string console.log('Searching by name:', idOrName); return this.items.find(user => user.name === idOrName); } else if (typeof idOrName === 'number') { // Search by id if idOrName is a number console.log('Searching by id:', idOrName); return this.items.find(user => user.id === idOrName); } return undefined; // Return undefined if no match found } } // Usage const userRepo = new UserRepository(); userRepo.add({ id: 1, name: "Alice" }); userRepo.add({ id: 2, name: "Bob" }); const user1 = userRepo.getById(1); const user2 = userRepo.getById("Bob"); console.log(user1); // Output: { id: 1, name: "Alice" } console.log(user2); // Output: { id: 2, name: "Bob" }
4.Classes génériques : : utilisez ceci lorsque vous souhaitez que toutes les propriétés de votre classe adhèrent au type spécifié par le paramètre générique. Cela permet une certaine flexibilité tout en garantissant que chaque propriété de la classe correspond au type transmis à la classe.
interface User { id: number; name: string; age: number; } class UserDetails<T extends User> { id: T['id']; name: T['name']; age: T['age']; constructor(user: T) { this.id = user.id; this.name = user.name; this.age = user.age; } // Method to get user details getUserDetails(): string { return `User: ${this.name}, ID: ${this.id}, Age: ${this.age}`; } // Method to update user name updateName(newName: string): void { this.name = newName; } // Method to update user age updateAge(newAge: number): void { this.age = newAge; } } // Using the UserDetails class with a User type const user: User = { id: 1, name: "Alice", age: 30 }; const userDetails = new UserDetails(user); console.log(userDetails.getUserDetails()); // Output: "User: Alice, ID: 1, Age: 30" // Updating user details userDetails.updateName("Bob"); userDetails.updateAge(35); console.log(userDetails.getUserDetails()); // Output: "User: Bob, ID: 1, Age: 35" console.log(new UserDetails("30")); // Error: "This will throw error"
5.Contraindre les paramètres de type aux types transmis : Parfois, nous souhaitons qu'un type de paramètre dépende d'autres paramètres transmis. Cela semble déroutant, voyons l'exemple ci-dessous.
function getProperty<Type>(obj: Type, key: keyof Type) { return obj[key]; } let x = { a: 1, b: 2, c: 3 }; getProperty(x, "a"); // Valid getProperty(x, "d"); // Error: Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.
6.Types conditionnels : Souvent, nous voulons que nos types soient soit d'un type, soit d'un autre. Dans de telles situations, nous utilisons des types conditionnels.
Un exemple simple serait :
function func(param:number|boolean){ return param; } console.log(func(2)) //Output: 2 will be printed console.log(func("True")) //Error: boolean cannot be passed as argument
Exemple un peu complexe :
type HasProperty<T, K extends keyof T> = K extends "age" ? "Has Age" : "Has Name"; interface User { name: string; age: number; } let test1: HasProperty<User, "age">; // "Has Age" let test2: HasProperty<User, "name">; // "Has Name" let test3: HasProperty<User, "email">; // Error: Type '"email"' is not assignable to parameter of type '"age" | "name"'.
6.Types d'intersection : Ces types sont utiles lorsque nous souhaitons combiner plusieurs types en un seul, permettant à un type particulier d'hériter des propriétés et des comportements de divers autres types.
Voyons un exemple intéressant pour cela :
// Defining the types for each area of well-being interface MentalWellness { mindfulnessPractice: boolean; stressLevel: number; // Scale of 1 to 10 } interface PhysicalWellness { exerciseFrequency: string; // e.g., "daily", "weekly" sleepDuration: number; // in hours } interface Productivity { tasksCompleted: number; focusLevel: number; // Scale of 1 to 10 } // Combining all three areas into a single type using intersection types type HealthyBody = MentalWellness & PhysicalWellness & Productivity; // Example of a person with a balanced healthy body const person: HealthyBody = { mindfulnessPractice: true, stressLevel: 4, exerciseFrequency: "daily", sleepDuration: 7, tasksCompleted: 15, focusLevel: 8 }; // Displaying the information console.log(person);
7.infer mot-clé : Le mot-clé infer est utile lorsque nous voulons déterminer conditionnellement un type spécifique, et lorsque la condition est remplie, il nous permet d'extraire des sous-types de ce type.
Voici la syntaxe générale :
type ConditionalType<T> = T extends SomeType ? InferredType : OtherType;
Exemple pour ceci :
type ReturnTypeOfPromise<T> = T extends Promise<infer U> ? U : number; type Result = ReturnTypeOfPromise<Promise<string>>; // Result is 'string' type ErrorResult = ReturnTypeOfPromise<number>; // ErrorResult is 'never' const result: Result = "Hello"; console.log(typeof result); // Output: 'string'
8.Type Variance : Ce concept explique comment le sous-type et le supertype sont liés les uns aux autres.
Ceux-ci sont de deux types :
Covariance : Un sous-type peut être utilisé là où un supertype est attendu.
Voyons un exemple pour cela :
function func<T>(args:T):T{ return args; }
Dans l'exemple ci-dessus, Car a hérité des propriétés de la classe Vehicle, il est donc absolument valable de l'attribuer au sous-type où le supertype est attendu, car le sous-type aurait toutes les propriétés d'un supertype.
Contravariance : C'est l'opposé de la covariance. Nous utilisons des supertypes aux endroits où le sous-type devrait être.
function func<T extends string | number>(value: T): T { return value; } const stringValue = func("Hello"); // Works, T is string const numberValue = func(42); // Works, T is number // const booleanValue = func(true); // Error: Type 'boolean' is not assignable to type 'string | number'
Lors de l'utilisation de la contravariance, nous devons faire attention à ne pas accéder aux propriétés ou aux méthodes spécifiques au sous-type, car cela pourrait entraîner une erreur.
9. Réflexions : Ce concept consiste à déterminer le type d'une variable au moment de l'exécution. Bien que TypeScript se concentre principalement sur la vérification des types au moment de la compilation, nous pouvons toujours exploiter les opérateurs TypeScript pour inspecter les types pendant l'exécution.
Opérateur typeof : Nous pouvons utiliser l'opérateur typeof pour trouver le type de variable au moment de l'exécution
// Generic interface with type parameters T and U interface Repository<T, U> { items: T[]; // Array of items of type T add(item: T): void; // Function to add an item of type T getById(id: U): T | undefined; // Function to get an item by ID of type U } // Implementing the Repository interface for a User entity interface User { id: number; name: string; } class UserRepository implements Repository<User, number> { items: User[] = []; add(item: User): void { this.items.push(item); } getById(idOrName: number | string): User | undefined { if (typeof idOrName === 'string') { // Search by name if idOrName is a string console.log('Searching by name:', idOrName); return this.items.find(user => user.name === idOrName); } else if (typeof idOrName === 'number') { // Search by id if idOrName is a number console.log('Searching by id:', idOrName); return this.items.find(user => user.id === idOrName); } return undefined; // Return undefined if no match found } } // Usage const userRepo = new UserRepository(); userRepo.add({ id: 1, name: "Alice" }); userRepo.add({ id: 2, name: "Bob" }); const user1 = userRepo.getById(1); const user2 = userRepo.getById("Bob"); console.log(user1); // Output: { id: 1, name: "Alice" } console.log(user2); // Output: { id: 2, name: "Bob" }
Opérateur instanceof : L'opérateur instanceof peut être utilisé pour vérifier si un objet est une instance d'une classe ou d'un type particulier.
interface User { id: number; name: string; age: number; } class UserDetails<T extends User> { id: T['id']; name: T['name']; age: T['age']; constructor(user: T) { this.id = user.id; this.name = user.name; this.age = user.age; } // Method to get user details getUserDetails(): string { return `User: ${this.name}, ID: ${this.id}, Age: ${this.age}`; } // Method to update user name updateName(newName: string): void { this.name = newName; } // Method to update user age updateAge(newAge: number): void { this.age = newAge; } } // Using the UserDetails class with a User type const user: User = { id: 1, name: "Alice", age: 30 }; const userDetails = new UserDetails(user); console.log(userDetails.getUserDetails()); // Output: "User: Alice, ID: 1, Age: 30" // Updating user details userDetails.updateName("Bob"); userDetails.updateAge(35); console.log(userDetails.getUserDetails()); // Output: "User: Bob, ID: 1, Age: 35" console.log(new UserDetails("30")); // Error: "This will throw error"
Nous pouvons utiliser une bibliothèque tierce pour déterminer les types au moment de l'exécution.
10.Injection de dépendance : L'injection de dépendances est un modèle qui vous permet d'introduire du code dans votre composant sans réellement le créer ou le gérer. Même si cela peut ressembler à l'utilisation d'une bibliothèque, c'est différent car vous n'avez pas besoin de l'installer ou de l'importer via un CDN ou une API.
À première vue, cela peut également sembler similaire à l'utilisation de fonctions pour la réutilisabilité, car les deux permettent la réutilisation du code. Cependant, si nous utilisons des fonctions directement dans nos composants, cela peut conduire à un couplage étroit entre elles. Cela signifie que tout changement dans la fonction ou dans sa logique pourrait avoir un impact sur chaque endroit où elle est utilisée.
L'injection de dépendances résout ce problème en dissociant la création de dépendances des composants qui les utilisent, rendant le code plus maintenable et testable.
Exemple sans injection de dépendances
function getProperty<Type>(obj: Type, key: keyof Type) { return obj[key]; } let x = { a: 1, b: 2, c: 3 }; getProperty(x, "a"); // Valid getProperty(x, "d"); // Error: Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.
Exemple avec injection de dépendances
function func(param:number|boolean){ return param; } console.log(func(2)) //Output: 2 will be printed console.log(func("True")) //Error: boolean cannot be passed as argument
Dans un scénario étroitement couplé, si vous avez un attribut stressLevel dans la classe MentalWellness aujourd'hui et décidez de le remplacer par autre chose demain, vous devrez mettre à jour tous les endroits où il a été utilisé. Cela peut entraîner de nombreux problèmes de refactorisation et de maintenance.
Cependant, avec l'injection de dépendances et l'utilisation d'interfaces, vous pouvez éviter ce problème. En passant les dépendances (telles que le service MentalWellness) via le constructeur, les détails spécifiques de l'implémentation (comme l'attribut stressLevel) sont abstraits derrière l'interface. Cela signifie que les modifications apportées à l'attribut ou à la classe ne nécessitent pas de modifications dans les classes dépendantes, tant que l'interface reste la même. Cette approche garantit que le code est faiblement couplé, plus maintenable et plus facile à tester, car vous injectez ce qui est nécessaire au moment de l'exécution sans coupler étroitement les composants.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!