01 - any, unknown, never : le trio que personne ne comprend
Ce que tu vas apprendre
- Pourquoi
anydétruit silencieusement ton typage - Comment
unknownte protégé là oùanyte trahit - A quoi sert
never(et pourquoi tu l'as deja utilise sans le savoir) - Les regles concrètes pour choisir entre les trois
Prerequisites
Avoir lu l'article d'introduction de cette serie.
any : la trappe de secours qui devient un trou dans la coque
Quand tu ecris any, tu dis au compilateur : "ne vérifié rien sur cette valeur". Ca compile, ca passe, et ca plante au runtime.
typescriptconst data: any = { name: "Nicolas" }
console.log(data.age.toString()) // compile ✅ — crash au runtime ❌
Le compilateur ne bronche pas. data.age est undefined, et .toString() sur undefined lance un TypeError. Exactement le genre d'erreur que TypeScript est cense empecher.
Le vrai danger de any, c'est la contamination. Il se propage à travers les appels de fonctions :
typescriptfunction getConfig(): any {
return JSON.parse(readFileSync("config.json", "utf-8"))
}
const config = getConfig()
const port = config.server.port // type: any
const host = config.server.host // type: any
const timeout = port.split(":") // compile ✅ — .split() sur un number, crash ❌
A partir du moment ou getConfig retourne any, tout ce qui en decoule perd son typage. Le compilateur ne te previent plus de rien. Tu as TypeScript dans le nom de tes fichiers et JavaScript dans le comportement de ton code.
J'ai vu des projets ou un seul any dans une fonction utilitaire contaminait 200 fichiers. Le dev n'avait aucune idee que ses types etaient devenus fictifs.
Quand any est acceptable
Presque jamais. Les seuls cas ou je toléré any :
- Migration progressive d'une codebase JavaScript (avec un
// TODO: type thisa cote) - Contournement temporaire d'un bug dans les types d'une lib tierce
- Un
catchou tu dois manipuler une erreur dont le type est inconnu (et encore,unknownest mieux)
Si tu utilises any pour autre chose, c'est un signal que tu n'as pas encore trouve le bon type. L'article sur les generics couvre la plupart des cas ou les devs mettent any par reflexe.
unknown : le any qui te force a vérifier
unknown est l'oppose de any en termes de sécurité. Une valeur unknown peut contenir n'importe quoi, comme any. Mais tu ne peux rien en faire sans vérifier son type d'abord.
typescriptconst data: unknown = JSON.parse('{"name": "Nicolas"}')
// ❌ Erreur de compilation
console.log(data.name)
// ✅ Tu dois verifier d'abord
if (typeof data === "object" && data !== null && "name" in data) {
console.log(data.name)
}
Le compilateur te force a prouver le type avant de l'utiliser. C'est exactement ce qu'on veut quand on recoit des donnees de l'extérieur : une réponse API, un fichier JSON, un localStorage.getItem, un body de requête HTTP.
unknown vs any en pratique
Prenons un parser de configuration :
typescript// ❌ Avec any — aucune protection
function parseConfig(raw: string): any {
return JSON.parse(raw)
}
const config = parseConfig(input)
config.server.port // compile, mais peut crasher
// ✅ Avec unknown — le compilateur te guide
function parseConfig(raw: string): unknown {
return JSON.parse(raw)
}
const config = parseConfig(input)
// Tu dois verifier la structure
if (
typeof config === "object" &&
config !== null &&
"server" in config &&
typeof config.server === "object" &&
config.server !== null &&
"port" in config.server
) {
console.log(config.server.port) // type: unknown, mais au moins c'est safe
}
C'est plus verbeux. C'est fait expres. Si tu veux quelque chose de plus ergonomique pour la validation, la serie couvre Zod dans la sous-serie pratique. Mais le principe est la : unknown te force a écrire le code de validation que tu aurais du écrire de toute facon.
Depuis TypeScript 4.4, le compilateur infere unknown pour les variables de catch quand useUnknownInCatchVariables est active dans le tsconfig (inclus dans strict). Avant, err dans un catch etait any par défaut.
typescripttry {
riskyOperation()
} catch (err) {
// Avec strict: true, err est unknown
if (err instanceof Error) {
console.log(err.message) // ✅ safe
}
}
never : le type de ce qui n'arrive jamais
never est le type le plus abstrait des trois. Il represente quelque chose qui ne peut pas exister. Une fonction qui lance toujours une exception retourne never. Un cas dans un switch qui ne devrait jamais etre atteint a le type never.
typescriptfunction throwError(message: string): never {
throw new Error(message)
}
function fail(): never {
while (true) {
// boucle infinie — la fonction ne retourne jamais
}
}
Ca peut sembler theorique. En pratique, never est ton meilleur allie pour l'exhaustivite.
Le pattern exhaustive check
Imagine un système de notifications avec trois canaux :
typescripttype Channel = "email" | "sms" | "push"
function send(channel: Channel, message: string) {
switch (channel) {
case "email":
sendEmail(message)
break
case "sms":
sendSms(message)
break
case "push":
sendPush(message)
break
default: {
const _exhaustive: never = channel
throw new Error(`Canal inconnu : ${_exhaustive}`)
}
}
}
Si tu ajoutes "slack" a l'union Channel sans ajouter le case correspondant, le compilateur refuse. La variable channel dans le default aurait le type "slack", qui n'est pas assignable a never. Tu as un filet de sécurité a la compilation.
J'utilise ce pattern sur paltemps.fr pour tous les switchs sur des unions. Ca m'a sauve plusieurs fois quand j'ajoutais un nouveau type d'événement ou un nouveau statut de commande.
never dans le système de types
never est le "bottom type" de TypeScript. Il est assignable a tout, mais rien n'est assignable a never (sauf never lui-meme). C'est utile pour comprendre certains comportements :
typescripttype A = string & number // never — aucune valeur n'est a la fois string et number
type B = never | string // string — never disparait des unions
Tu retrouveras never dans les conditional types pour filtrer des types. Par exemple, le type utilitaire Exclude fonctionne en transformant les types non voulus en never :
typescripttype Exclude<T, U> = T extends U ? never : T
type Result = Exclude<"a" | "b" | "c", "a"> // "b" | "c"
Le spectre des permissions
Pour résumer la relation entre les trois :
any → tout est permis, rien n'est verifie
unknown → rien n'est permis sans verification
never → rien n'est possible (la valeur n'existe pas)
any est en haut du spectre (aucune contrainte). never est en bas (contrainte maximale). unknown est au milieu : la valeur existe, mais tu dois prouver son type.
En tant que regle personnelle : je n'utilise jamais any dans du code nouveau. unknown pour les donnees exterieures, never pour l'exhaustivite, et des types concrets pour tout le reste. Si le type est "trop complique", c'est un signal que j'ai besoin de generics, pas de any.
Résumé
anydésactivé le compilateur et contamine les types en cascade — a éviter dans du code nouveauunknownforce la vérification du type avant utilisation — a utiliser pour toute donnee extérieure (API, JSON, user input)neverrepresente l'impossible et sert de filet de sécurité pour les verifications d'exhaustivite- Active
strict: truedans ton tsconfig pour quecatchutiliseunknownpar défaut
Article précédent : 00 - TypeScript ne se résumé pas a ajouter : string
Article suivant : 02 - Inference, widening et narrowing