Types Avancés en Typescript
Sommaire
Salut à tous ! Aujourd'hui, on va plonger dans les types avancés en Typescript.
Préparez-vous à embarquer pour une aventure intergalactique où nous allons découvrir des concepts complexes et puissants pour rendre notre code encore plus robuste et flexible. 🚀
Types Génériques
Les types génériques permettent de créer des composants réutilisables qui fonctionnent avec différents types de données. C'est un peu comme avoir un vaisseau spatial qui peut s'adapter à toutes sortes de missions interstellaires.
Fonctions Génériques
Imaginons que nous avons une mission pour collecter des échantillons de différents types de planètes. Voici comment nous pourrions définir une fonction générique pour cette mission :
function collectSamples<T>(sample: T): T[] {
return [sample]
}
let mineralSamples = collectSamples<string>('Quartz')
let alienSamples = collectSamples<number>(42)
console.log(mineralSamples) // ["Quartz"]
console.log(alienSamples) // [42]
Classes Génériques
Notre vaisseau spatial peut également transporter différentes cargaisons. Utilisons une classe générique pour gérer cela :
class CargoBay<T> {
private items: T[] = []
addItem(item: T): void {
this.items.push(item)
}
getItems(): T[] {
return this.items
}
}
let foodCargo = new CargoBay<string>()
foodCargo.addItem('Apples')
foodCargo.addItem('Bananas')
let equipmentCargo = new CargoBay<number>()
equipmentCargo.addItem(1001)
equipmentCargo.addItem(1002)
console.log(foodCargo.getItems()) // ["Apples", "Bananas"]
console.log(equipmentCargo.getItems()) // [1001, 1002]
Contraintes Génériques
Les contraintes génériques permettent de restreindre les types qui peuvent être utilisés dans des composants génériques. Imaginons que notre vaisseau ne peut transporter que des objets qui ont une propriété name
:
interface Named {
name: string
}
class NamedCargoBay<T extends Named> {
private items: T[] = []
addItem(item: T): void {
this.items.push(item)
}
getItems(): T[] {
return this.items
}
}
let robotCargo = new NamedCargoBay<{ name: string; model: string }>()
robotCargo.addItem({ name: 'Robot-1', model: 'XR-7' })
robotCargo.addItem({ name: 'Robot-2', model: 'XR-8' })
console.log(robotCargo.getItems())
Types Conditionnels
Les types conditionnels permettent de choisir un type basé sur une condition. C'est comme si notre vaisseau pouvait changer de forme en fonction de la planète où il atterrit.
type PlanetType<T> = T extends 'Earth' ? 'Habitable' : 'Inhabitable'
let earthType: PlanetType<'Earth'> = 'Habitable'
let marsType: PlanetType<'Mars'> = 'Inhabitable'
Utilisation Avancée des Types Conditionnels
Les types conditionnels peuvent devenir très puissants lorsqu'ils sont combinés avec d'autres types avancés. Par exemple, on peut créer des types pour vérifier la compatibilité entre différents composants de notre vaisseau :
type ComponentCompatibility<C1, C2> = C1 extends C2 ? true : false
type Engine = { type: 'engine'; power: number }
type Hull = { type: 'hull'; durability: number }
type Test1 = ComponentCompatibility<Engine, Engine> // true
type Test2 = ComponentCompatibility<Engine, Hull> // false
Types Mappés
Les types mappés permettent de créer de nouveaux types basés sur des types existants, en transformant chaque propriété. C'est un peu comme terraformer une planète pour la rendre habitable.
type SpaceStation = {
name: string
crew: number
operational: boolean
}
type ReadonlySpaceStation = {
readonly [P in keyof SpaceStation]: SpaceStation[P]
}
let station: ReadonlySpaceStation = {
name: 'Alpha',
crew: 100,
operational: true,
}
// station.crew = 101; // Erreur : la propriété est en lecture seule
Types Mappés avec Transformation
On peut également utiliser des types mappés pour transformer les types des propriétés. Par exemple, pour rendre toutes les propriétés optionnelles :
type Optional<T> = {
[P in keyof T]?: T[P]
}
type OptionalSpaceStation = Optional<SpaceStation>
let station2: OptionalSpaceStation = {
name: 'Beta',
// Le reste des propriétés est optionnel
}
Les Types Utilitaires Intégrés
Typescript offre plusieurs types utilitaires intégrés qui nous facilitent la vie ! Mangez-en, c’est bon !
Partial
Partial
rend toutes les propriétés d'un type optionnelles :
type Spaceship = {
model: string
speed: number
fuel: number
}
let partialShip: Partial<Spaceship> = {
model: 'X-Wing',
}
Readonly
Readonly
rend toutes les propriétés d'un type immuables :
type Mission = {
objective: string
duration: number
}
let mission: Readonly<Mission> = {
objective: 'Explore Mars',
duration: 30,
}
// mission.duration = 40; // Erreur : la propriété est en lecture seule
Pick et Omit
Pick
permet de créer un nouveau type en sélectionnant un sous-ensemble des propriétés d'un type existant :
type Pilot = {
name: string
rank: string
flyingHours: number
}
type BasicPilotInfo = Pick<Pilot, 'name' | 'rank'>
let pilot: BasicPilotInfo = {
name: 'John',
rank: 'Captain',
}
Omit
fait l'inverse de Pick
et permet de créer un nouveau type en omettant certaines propriétés d'un type existant :
type DetailedPilotInfo = Omit<Pilot, 'rank'>
let detailedPilot: DetailedPilotInfo = {
name: 'John',
flyingHours: 1500,
}
Les Types Intersections
Les types intersections permettent de combiner plusieurs types en un seul, plutôt pratique !
type Pilot = {
fly(): void
}
type Engineer = {
repair(): void
}
type SpaceCrew = Pilot & Engineer
let crewMember: SpaceCrew = {
fly: () => console.log('Flying the spaceship'),
repair: () => console.log('Repairing the spaceship'),
}
crewMember.fly()
crewMember.repair()
Conclusion
Voilà, on a fait un tour des types avancés en Typescript !
Ces concepts puissants te permettent de rendre ton code plus flexible et sûr. En approfondissant notre compréhension des types génériques, conditionnels, mappés et des types utilitaires, on peut construire des applications plus robustes et maintenables.