Types Avancés en Typescript

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.

Tags

  • tutorial
  • typescript

Cet article à été posté le