11 - Union vs intersection : quand utiliser | et quand utiliser &
Ce que tu vas apprendre
- Ce que
|(union) et&(intersection) font réellement sur les types - Pourquoi l'intersection d'objets ajoute des propriétés (et pas l'inverse)
- Les pièges avec les types primitifs et les unions
- Comment choisir entre les deux dans des cas concrets
Prerequisites
Avoir lu les articles sur les discriminated unions et les utility types.
La confusion la plus courante
Quand un junior voit A | B, il pense "A et B combines". Quand il voit A & B, il pense "ce qui est commun entre A et B". C'est l'inverse.
| (union) signifie : "la valeur est de type A ou de type B". Tu ne peux utiliser que ce qui est commun aux deux.
& (intersection) signifie : "la valeur est de type A et de type B". Tu as acces a tout.
typescriptinterface HasName { name: string }
interface HasEmail { email: string }
// Union : l'un OU l'autre
type Contact = HasName | HasEmail
const c: Contact = { name: "Nicolas" } // ✅
// c.name → ❌ pas garanti (c pourrait etre HasEmail)
// c.email → ❌ pas garanti (c pourrait etre HasName)
// Intersection : l'un ET l'autre
type FullContact = HasName & HasEmail
const f: FullContact = { name: "Nicolas", email: "n@n.dev" } // ✅
// f.name → ✅ garanti
// f.email → ✅ garanti
Union en détail
L'union A | B accepte une valeur qui satisfait A, ou B, ou les deux. Mais tu ne peux acceder qu'aux propriétés communes sans narrowing.
typescriptinterface Dog {
name: string
breed: string
}
interface Cat {
name: string
indoor: boolean
}
type Pet = Dog | Cat
function greet(pet: Pet) {
console.log(pet.name) // ✅ commun aux deux
console.log(pet.breed) // ❌ n'existe pas sur Cat
console.log(pet.indoor) // ❌ n'existe pas sur Dog
}
Pour acceder aux propriétés spécifiques, il faut narrower :
typescriptfunction describe(pet: Pet) {
if ("breed" in pet) {
// pet est Dog
console.log(`${pet.name} est un ${pet.breed}`)
} else {
// pet est Cat
console.log(`${pet.name} vit ${pet.indoor ? "dedans" : "dehors"}`)
}
}
Unions de primitifs
Les unions de primitifs sont simples et courantes :
typescripttype StringOrNumber = string | number
function format(value: StringOrNumber): string {
if (typeof value === "string") {
return value.toUpperCase()
}
return value.toFixed(2)
}
Les unions litterales sont les plus utiles :
typescripttype Direction = "north" | "south" | "east" | "west"
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"
type Status = "idle" | "loading" | "success" | "error"
Le compilateur refuse toute valeur qui n'est pas dans l'union.
Intersection en détail
L'intersection A & B créé un type qui combine toutes les propriétés de A et B. La valeur doit satisfaire les deux types simultanément.
typescriptinterface Timestamped {
createdAt: Date
updatedAt: Date
}
interface SoftDeletable {
deletedAt: Date | null
}
type User = {
id: string
name: string
email: string
}
type FullUser = User & Timestamped & SoftDeletable
// {
// id: string
// name: string
// email: string
// createdAt: Date
// updatedAt: Date
// deletedAt: Date | null
// }
C'est comme de la composition. Tu construis un type complexe en combinant des morceaux independants. C'est un pattern que j'utilise sur paltemps.fr pour ajouter des metadonnees aux entités sans dupliquer les champs dans chaque interface.
Intersection de types incompatibles
Quand deux types ont une propriété avec le meme nom mais des types différents, l'intersection peut devenir never :
typescripttype A = { id: string }
type B = { id: number }
type C = A & B
// C.id est string & number = never
// C est utilisable en theorie mais aucune valeur ne peut satisfaire id
Le compilateur ne refuse pas le type C, mais tu ne pourras jamais créer une valeur valide.
Intersection de primitifs
L'intersection de deux primitifs incompatibles donne never :
typescripttype Impossible = string & number // never
Aucune valeur n'est a la fois string et number. never represente cet ensemble vide (vu dans l'article 01).
Pourquoi c'est contre-intuitif
L'intuition mathematique nous trompe. En theorie des ensembles :
- L'union de deux ensembles = tous les éléments des deux → plus grand
- L'intersection de deux ensembles = les éléments communs → plus petit
Mais en TypeScript, on parle de l'ensemble des valeurs valides :
string | number: toutes les strings ET tous les numbers → plus de valeurs possibles → moins de propriétés accessiblesHasName & HasEmail: seulement les objets qui ont name ET email → moins de valeurs possibles → plus de propriétés accessibles
Plus le type est restrictif sur les valeurs (intersection), plus tu as d'informations sur la structure. Plus le type est permissif sur les valeurs (union), moins tu sais ce qui est dedans.
Patterns pratiques
Composition de types avec &
typescript// Types de base reutilisables
interface WithId { id: string }
interface WithTimestamps {
createdAt: Date
updatedAt: Date
}
// Composition
type User = WithId & WithTimestamps & {
name: string
email: string
}
type Post = WithId & WithTimestamps & {
title: string
content: string
authorId: string
}
Extension de types tiers avec &
Quand tu veux ajouter des propriétés a un type que tu ne contrôles pas :
typescriptimport type { Request } from "express"
type AuthenticatedRequest = Request & {
user: { id: string; role: string }
}
function handler(req: AuthenticatedRequest) {
console.log(req.user.id) // ✅
console.log(req.body) // ✅ proprietes Express toujours la
}
Union pour les overloads simplifies
typescripttype Input = string | string[] | Record<string, string>
function normalize(input: Input): string[] {
if (typeof input === "string") return [input]
if (Array.isArray(input)) return input
return Object.values(input)
}
Union vs intersection : guide de choix
| Situation | Utilise |
|---|---|
| Plusieurs états possibles | | (union) |
| Valeurs litterales restreintes | | (union) |
| Combiner des traits/mixins | & (intersection) |
| Ajouter des propriétés a un type existant | & (intersection) |
| Paramètres qui acceptent plusieurs formes | | (union) |
| Retour qui garantit plusieurs propriétés | & (intersection) |
En général : | pour les paramètres d'entree (flexibilité), & pour les types de donnees internes (precision).
Résumé
|(union) : la valeur est l'un ou l'autre type — tu n'accedes qu'aux propriétés communes&(intersection) : la valeur satisfait les deux types — tu accedes a toutes les propriétés- L'intersection de propriétés incompatibles produit
never - L'intuition "union = plus, intersection = moins" s'inverse quand on parle des propriétés accessibles
- Utilise
|pour les paramètres flexibles et&pour la composition de types
Article précédent : 10 - Utility types
Article suivant : 12 - Enums vs unions litterales