JavaScript est typé dynamiquement, ce qui est vraiment utile — vous pouvez itérer rapidement, prototyper librement et déployer sans étape de compilation. Mais cette flexibilité a un coût : toute une classe d'erreurs ne se manifeste qu'à l'exécution. Une fonction attend une chaîne, reçoit undefined, et votre application plante en production à 2h du matin. TypeScript ajoute un système de types statiques par-dessus JavaScript qui détecte exactement ces erreurs au moment de la compilation, dans votre éditeur, avant que quoi que ce soit ne soit déployé. Cet article explique ce que cela signifie concrètement — avec du vrai code, des compromis honnêtes, et sans tapage.

Ce qu'est réellement TypeScript

TypeScript est un sur-ensemble de JavaScript : chaque fichier .js valide est aussi un fichier .ts valide. Vous pouvez renommer un fichier, n'ajouter aucune annotation de type, et il compile sans problème. Ce que TypeScript ajoute — annotations de types, interfaces, génériques, enums — est entièrement supprimé à la compilation. Le résultat est du JavaScript ordinaire. TypeScript ne change pas ce qui s'exécute ; il change ce que vous pouvez savoir avant l'exécution.

Le compilateur est tsc, installé via npm. Vous le pointez sur vos fichiers .ts et il produit des fichiers .js que votre runtime (Node.js, un navigateur, Deno) peut exécuter. Aucune syntaxe spécifique à TypeScript n'atteint jamais la production — les types n'existent que pour le développeur et la chaîne d'outils. Le dépôt TypeScript sur GitHub est lui-même un grand projet TypeScript, ce qui vous donne une idée de son passage à l'échelle.

bash
# Compile a single file
npx tsc src/index.ts

# Compile a whole project (uses tsconfig.json)
npx tsc

# Watch mode — recompile on every save
npx tsc --watch

Le bénéfice principal : les erreurs avant l'exécution

Voici le problème que TypeScript résout le plus visiblement. Imaginez que vous travaillez avec une réponse d'API où un champ pourrait être une chaîne ou null. En JavaScript ordinaire, cela échoue silencieusement en production :

js
// JavaScript — no error until this actually runs with a null value
function formatDisplayName(user) {
  return user.displayName.toUpperCase(); // 💥 TypeError if displayName is null
}

// This looks fine in isolation. The bug only appears when a user
// has no display name set — which might be rare, but it will happen.
const name = formatDisplayName({ displayName: null });

TypeScript détecte cela avant l'exécution du code :

ts
interface User {
  id: number;
  displayName: string | null;
  email: string;
}

function formatDisplayName(user: User): string {
  return user.displayName.toUpperCase();
  // ❌ TypeScript error: Object is possibly 'null'.
  //    Property 'toUpperCase' does not exist on type 'null'.
}

// Fix: handle the null case explicitly
function formatDisplayName(user: User): string {
  if (user.displayName === null) {
    return user.email; // fall back to email
  }
  return user.displayName.toUpperCase(); // TypeScript now knows this is safe
}

C'est le moment « aha » pour la plupart des développeurs. Le message d'erreur vous indique exactement ce qui ne va pas et où — dans votre éditeur, avant d'exécuter une seule ligne. Avec le mode strict activé, TypeScript est particulièrement agressif pour détecter les problèmes null et undefined via sa fonctionnalité strictNullChecks, activée par défaut en mode strict.

Le modèle à intérioriser : les erreurs TypeScript sont au moment de la compilation, pas à l'exécution. Une erreur de tsc signifie que votre code a une incohérence de type — TypeScript peut prouver qu'il va échouer (ou pourrait échouer) sans l'exécuter. Corrigez l'erreur de type, et vous aurez éliminé toute la classe de bogues.

Le système de types TypeScript en un coup d'œil

Vous n'avez pas besoin d'annoter tout. TypeScript infère les types à partir des affectations, des valeurs de retour, et des appels de fonctions — souvent vous écrivez juste du JavaScript normal et obtenez la vérification de type gratuitement. Mais connaître la syntaxe d'annotation aide quand l'inférence ne suffit pas.

ts
// Primitives
const productName: string  = 'Wireless Keyboard';
const price:       number  = 79.99;
const inStock:     boolean = true;

// TypeScript infers these without annotations — same effect
const productName = 'Wireless Keyboard'; // inferred: string
const price       = 79.99;               // inferred: number

// Arrays
const tags:     string[]  = ['electronics', 'peripherals'];
const ratings:  number[]  = [4.5, 4.8, 4.2];

// Objects with interfaces
interface Product {
  id:          number;
  name:        string;
  price:       number;
  category:    string;
  inStock:     boolean;
  description: string | null; // union type — string or null
}

// Union types — a value that could be one of several types
type Status = 'active' | 'inactive' | 'pending'; // string literal union
type ID     = string | number;                    // string or number

// Function with typed parameters and return type
function formatProductCard(product: Product): string {
  const stockLabel = product.inStock ? 'In Stock' : 'Out of Stock';
  const desc       = product.description ?? 'No description available';
  return `${product.name} — $${product.price.toFixed(2)} (${stockLabel})
${desc}`;
}

// Generic function — works with any type, preserves it
function firstOrDefault<T>(items: T[], fallback: T): T {
  return items.length > 0 ? items[0] : fallback;
}

const first = firstOrDefault(['a', 'b', 'c'], 'z'); // inferred: string
const num   = firstOrDefault([1, 2, 3],       0);   // inferred: number

Le manuel TypeScript approfondit le système de types — il est vraiment bien écrit et vaut la peine d'être lu une fois que vous avez les bases.

Configurer TypeScript dans un projet

Ajouter TypeScript à un projet prend environ cinq minutes. Vous installez le compilateur comme dépendance de développement, générez un fichier de configuration et commencez à renommer les fichiers .js en .ts au rythme qui convient.

bash
# Install TypeScript as a dev dependency
npm install -D typescript

# Generate tsconfig.json with sensible defaults
npx tsc --init

Le tsconfig.json généré contient des dizaines d'options, la plupart commentées. Les principales à connaître :

json
{
  "compilerOptions": {
    "target": "ES2020",       // what JavaScript version to output
    "module": "commonjs",     // module system (commonjs for Node, ESNext for bundlers)
    "outDir": "./dist",       // where compiled .js files go
    "rootDir": "./src",       // where your .ts source files live
    "strict": true,           // enables all strict type checks — highly recommended
    "esModuleInterop": true,  // smoother interop with CommonJS modules
    "skipLibCheck": true      // skip type checks on .d.ts files in node_modules
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Activez toujours strict: true sur un nouveau projet. Cela regroupe plusieurs vérifications dont strictNullChecks, noImplicitAny et strictFunctionTypes. Sur une base de code existante, vous devrez peut-être les activer progressivement — mais pour tout ce qui est en greenfield, commencez strict.

TypeScript vs JavaScript — les vrais compromis

TypeScript présente de nombreux avantages réels, mais aussi de vrais coûts. Voici un bilan honnête :

  • Autocomplétion qui fonctionne vraiment. Votre éditeur connaît la forme de chaque objet, il peut donc suggérer les bons noms de propriétés et signatures de méthodes au lieu de deviner. Cela seul fait gagner du temps chaque jour.
  • Bogues détectés avant d'atteindre la production. Déréférences null, types d'arguments erronés, propriétés requises manquantes — TypeScript les signale à l'écriture, pas à 3h dans un canal d'incident.
  • Refactorisation plus sûre. Renommez un champ dans une interface et TypeScript signale immédiatement tous les appels qui nécessitent une mise à jour. Dans une grande base de code JavaScript, ce genre de modification est terrifiant.
  • Le code se documente lui-même. Une signature de fonction comme sendEmail(to: string, subject: string, body: string, options?: EmailOptions): Promise<SendResult> vous dit tout ce que vous devez savoir sans lire l'implémentation.
  • Meilleur pour les équipes. Quand plusieurs développeurs partagent une base de code, les types forment un contrat entre les modules. Vous pouvez refactoriser votre code sans casser celui de votre collègue.

Et les coûts honnêtes :

  • Ça ajoute une étape de compilation. Pour un petit script ou un prototype rapide, exécuter tsc avant de pouvoir tester est une vraie friction. Le JavaScript vanilla s'exécute immédiatement.
  • La configuration initiale prend du temps. Configurer tsconfig.json, ajouter des définitions de types pour les bibliothèques tierces (@types/express, etc.) et configurer votre éditeur correctement prend quelques heures de travail.
  • any compromet tout le dispositif. TypeScript a une échappatoire — vous pouvez typer n'importe quoi comme any, ce qui désactive la vérification de type pour cette valeur. Une utilisation excessive d'any signifie que vous avez la friction de TypeScript sans la sécurité. C'est une béquille, pas une solution.
  • Les types de bibliothèques tierces peuvent prendre du retard. Tous les packages npm ne fournissent pas des définitions TypeScript. Certains s'appuient sur des packages @types/* maintenus par la communauté qui peuvent être incomplets ou obsolètes.
Quand le JS vanilla convient : un petit script CLI, une migration de données ponctuelle, un prototype du week-end que vous jetterez. La valeur de TypeScript se compose avec la taille du projet et la taille de l'équipe. Un script de 200 lignes utilisé par un développeur n'a pas besoin de types. Une base de code de 50 000 lignes partagée par huit développeurs en a absolument besoin.

Là où TypeScript brille vraiment

TypeScript offre les meilleurs rendements dans trois situations : les grandes bases de code, les bibliothèques partagées, et tout ce qui sera refactorisé avec le temps. Le système de types agit comme une documentation vivante toujours à jour — contrairement aux commentaires ou aux fichiers README.

Un exemple pratique : votre application appelle une API REST qui retourne des données utilisateur. Sans TypeScript, vous faites confiance à l'API pour retourner ce que vous attendez. Avec TypeScript, vous modélisez la réponse et obtenez un retour immédiat si quelque chose ne correspond pas :

ts
interface ApiUser {
  id:        number;
  username:  string;
  email:     string;
  avatarUrl: string | null;
  createdAt: string; // ISO 8601 date string
  role:      'admin' | 'editor' | 'viewer';
}

interface ApiResponse<T> {
  data:    T;
  total:   number;
  page:    number;
  perPage: number;
}

async function fetchUsers(page: number): Promise<ApiResponse<ApiUser[]>> {
  const response = await fetch(`/api/users?page=${page}`);
  if (!response.ok) {
    throw new Error(`Failed to fetch users: ${response.status}`);
  }
  return response.json() as Promise<ApiResponse<ApiUser[]>>;
}

// Now every consumer of this function knows exactly what they'll get
const result = await fetchUsers(1);
result.data.forEach(user => {
  // TypeScript knows user.role is 'admin' | 'editor' | 'viewer'
  // It will error if you try to access a property that doesn't exist
  console.log(`${user.username} (${user.role})`);
});

Quand vous devez temporairement désactiver la vérification de type, utilisez unknown plutôt que any. La différence : any contourne silencieusement toutes les vérifications, tandis qu'unknown vous oblige à affiner le type avant d'utiliser la valeur — vous obtenez l'échappatoire sans perdre entièrement la sécurité.

ts
// ❌ any — TypeScript trusts you completely, no checks
function processData(data: any) {
  data.nonExistentMethod(); // no error — TypeScript looks away
}

// ✅ unknown — you have to prove what it is before using it
function processData(data: unknown) {
  if (typeof data === 'string') {
    console.log(data.toUpperCase()); // safe — TypeScript knows it's a string here
  } else if (Array.isArray(data)) {
    console.log(data.length);        // safe — TypeScript knows it's an array
  }
}

Le Playground TypeScript est la façon la plus rapide d'expérimenter avec ces patrons. Collez du code, voyez la sortie JavaScript compilée et les éventuelles erreurs de type en temps réel — aucune installation requise.

Conclusion

TypeScript n'est pas magique, et ce n'est pas adapté à tous les projets. Mais pour toute base de code JavaScript qui grandit, est maintenue par plus d'une personne, ou sera refactorisée — il change vraiment l'expérience de développement. Le système de types détecte toute une catégorie de bogues d'exécution avant qu'ils ne soient déployés, rend l'autocomplétion vraiment utile, et transforme la refactorisation d'un processus manuel risqué en quelque chose que la chaîne d'outils gère.

Commencez avec la documentation officielle TypeScript — elle est bien structurée et adaptée aux débutants. Le Playground TypeScript est idéal pour expérimenter sans aucune configuration. L' entrée glossaire TypeScript de MDN est un bon aperçu en une page si vous voulez une autre perspective. Et si vous travaillez avec des données JSON en TypeScript, l'outil JSON vers TypeScript sur ce site génère des interfaces TypeScript directement depuis n'importe quelle charge utile JSON — utile quand vous devez modéliser une réponse API rapidement sans écrire les types à la main.