ITSkillsCenter
Blog

Pourquoi TypeScript change tout

8 دقائق للقراءة

Ce que vous saurez faire à la fin

  1. Comprendre pourquoi 80% des entreprises tech (Microsoft, Airbnb, Stripe, Wave) imposent TypeScript en 2026 et le valoriser sur votre CV.
  2. Installer TypeScript dans un projet JavaScript existant sans tout réécrire en 30 minutes maximum.
  3. Maîtriser les types essentiels (primitifs, objets, unions, génériques, utilitaires) pour 90% des cas d’usage en PME.
  4. Migrer progressivement un projet legacy de 50 fichiers JS vers TypeScript en gardant la production en marche.
  5. Configurer VS Code, ESLint et Prettier pour un workflow TypeScript moderne avec autocomplétion intelligente.

Durée : 4h. Pré-requis : JavaScript ES6+ (let, const, fonctions fléchées, async/await), Node.js 20+, npm, VS Code, un projet JS existant pour pratiquer (sinon vous en créerez un). Coût : 0 FCFA.

Étape 1 — Pourquoi TypeScript change tout

JavaScript est dynamiquement typé : une variable peut contenir un nombre puis une chaîne sans erreur. Cette flexibilité devient un cauchemar passé 1000 lignes de code, surtout en équipe. TypeScript ajoute un système de types qui détecte 80% des bugs avant l’exécution, directement dans VS Code.

// JavaScript : pas d'erreur tant que ça ne plante pas
function calculerTVA(prix, taux) {
  return prix * (1 + taux);
}
calculerTVA("5000", "0.18"); // résultat : "50000.18" (concaténation !)
calculerTVA(); // NaN, pas d'alerte
calculerTVA(5000); // NaN, pas d'alerte
// TypeScript : erreur dès la frappe
function calculerTVA(prix: number, taux: number): number {
  return prix * (1 + taux);
}
calculerTVA("5000", "0.18"); // ERREUR : Argument of type 'string' not assignable
calculerTVA(); // ERREUR : Expected 2 arguments, but got 0
calculerTVA(5000, 0.18); // 5900, OK

Étape 2 — Installation dans un projet existant

cd mon-projet-js
npm install -D typescript @types/node tsx
npx tsc --init

# Cela génère tsconfig.json
# Installer les types pour vos dépendances courantes :
npm install -D @types/express @types/lodash
// tsconfig.json (configuration recommandée pour PME)
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "allowJs": true,
    "checkJs": false,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Étape 3 — Types primitifs et inférence

// Inférence : TypeScript devine le type
const nom = "Boutique Dakar"; // string
const prix = 4500; // number
const actif = true; // boolean

// Annotation explicite quand nécessaire
let chiffreAffaires: number = 0;
let referenceClient: string;

// Tableaux
const produits: string[] = ["Riz", "Huile", "Tomate"];
const prixFCFA: Array<number> = [450000, 120000, 35000];

// Tuples : longueur fixe avec types par position
const coordonneesDakar: [number, number] = [14.6928, -17.4467];

// any et unknown : à éviter au maximum
let mauvais: any; // désactive TypeScript, perte de garanties
let prudent: unknown; // forcera une vérification avant usage

Étape 4 — Types pour objets et interfaces

// Type alias
type Produit = {
  id: number;
  nom: string;
  prixFCFA: number;
  enStock: boolean;
  categorie?: string; // optionnel grâce au ?
};

// Interface (équivalent pour objets, extensible)
interface Client {
  id: number;
  nom: string;
  email: string;
  telephone: string;
}

interface ClientPremium extends Client {
  remise: number;
  niveauFidelite: 'bronze' | 'argent' | 'or';
}

const oumar: ClientPremium = {
  id: 1,
  nom: "Oumar Diop",
  email: "oumar@gmail.com",
  telephone: "+221 77 123 45 67",
  remise: 10,
  niveauFidelite: 'or'
};

Étape 5 — Union types et narrowing

// Union : une variable peut être de plusieurs types
type ModePaiement = 'wave' | 'orange-money' | 'free-money' | 'carte' | 'especes';

function fraisPaiement(mode: ModePaiement): number {
  switch (mode) {
    case 'wave':
    case 'orange-money':
    case 'free-money':
      return 0; // gratuit pour le marchand
    case 'carte':
      return 200; // frais bancaires
    case 'especes':
      return 0;
    // TypeScript force à couvrir tous les cas
  }
}

// Narrowing : TypeScript affine le type au fur et à mesure
function afficherIdentifiant(id: number | string) {
  if (typeof id === 'string') {
    // Ici, TypeScript sait que id est string
    console.log(id.toUpperCase());
  } else {
    // Ici, id est forcément number
    console.log(id.toFixed(2));
  }
}

Étape 6 — Génériques pour code réutilisable

// Fonction générique : marche avec n'importe quel type
function premier<T>(liste: T[]): T | undefined {
  return liste[0];
}

const premierNom = premier(["Awa", "Moussa"]); // string | undefined
const premierPrix = premier([1500, 3500]); // number | undefined

// Type générique : utilitaire de réponse API
type ReponseAPI<T> = {
  ok: boolean;
  data?: T;
  erreur?: string;
};

async function fetchProduits(): Promise<ReponseAPI<Produit[]>> {
  try {
    const r = await fetch('/api/produits');
    const data: Produit[] = await r.json();
    return { ok: true, data };
  } catch (e) {
    return { ok: false, erreur: (e as Error).message };
  }
}

Étape 7 — Types utilitaires intégrés

// Partial : tous les champs deviennent optionnels
function modifierProduit(id: number, modifs: Partial<Produit>) {
  // ... mise à jour partielle en base
}
modifierProduit(42, { prixFCFA: 5500 }); // OK même sans nom ni id

// Pick : sélectionner des champs
type ProduitVitrine = Pick<Produit, 'nom' | 'prixFCFA'>;

// Omit : exclure des champs
type ProduitSansId = Omit<Produit, 'id'>;
function creerProduit(p: ProduitSansId): Produit {
  return { id: Date.now(), ...p };
}

// Required : tous les champs deviennent obligatoires
type ProduitComplet = Required<Produit>;

// Readonly : empêche la modification
const config: Readonly<{ apiUrl: string }> = {
  apiUrl: 'https://api.boutique-dakar.sn'
};
// config.apiUrl = "..."; // ERREUR

// Record : objet avec clés et valeurs typées
const stockParEntrepot: Record<string, number> = {
  'Dakar-Centre': 1200,
  'Pikine': 850,
  'Thies': 450
};

Étape 8 — Migration fichier par fichier

# Stratégie progressive : renommer .js en .ts un par un
mv src/utils/format.js src/utils/format.ts
# Lancer le compilateur en mode watch
npx tsc --noEmit --watch
# Corriger les erreurs UNE PAR UNE
// Avant migration : src/utils/format.js
export function formaterFCFA(montant) {
  return montant.toLocaleString('fr-SN') + ' FCFA';
}

// Après migration : src/utils/format.ts
export function formaterFCFA(montant: number): string {
  return montant.toLocaleString('fr-SN') + ' FCFA';
}

// Si vous bloquez sur un type complexe : @ts-expect-error TEMPORAIRE
// @ts-expect-error : à typer correctement plus tard (ticket #123)
const result = ancienneAPILegacy.callObscure(input);

Étape 9 — Types pour les API et formulaires

// Définir la forme de données reçues d'une API externe
interface CommandeWave {
  id: string;
  amount: number;
  currency: 'XOF';
  status: 'pending' | 'completed' | 'failed';
  customer_phone: string;
  created_at: string; // ISO 8601
}

async function recupererCommande(id: string): Promise<CommandeWave> {
  const r = await fetch(`https://api.wave.com/v1/checkout/sessions/${id}`, {
    headers: { Authorization: `Bearer ${process.env.WAVE_KEY}` }
  });
  if (!r.ok) throw new Error('Wave API error');
  return r.json() as Promise<CommandeWave>;
}

// Validation runtime avec Zod (recommandé en production)
import { z } from 'zod';

const ProduitSchema = z.object({
  nom: z.string().min(2),
  prixFCFA: z.number().int().positive(),
  enStock: z.boolean()
});

type ProduitValide = z.infer<typeof ProduitSchema>;

function ajouterProduit(donnees: unknown): ProduitValide {
  return ProduitSchema.parse(donnees); // throw si invalide
}

Étape 10 — Types pour React

// Composant fonctionnel typé
type CarteProduitProps = {
  produit: Produit;
  onAjouter?: (id: number) => void;
};

function CarteProduit({ produit, onAjouter }: CarteProduitProps) {
  return (
    <article>
      <h3>{produit.nom}</h3>
      <p>{produit.prixFCFA.toLocaleString()} FCFA</p>
      <button onClick={() => onAjouter?.(produit.id)}>
        Ajouter au panier
      </button>
    </article>
  );
}

// useState typé
import { useState } from 'react';
const [panier, setPanier] = useState<Produit[]>([]);
const [erreur, setErreur] = useState<string | null>(null);

Étape 11 — Configurer ESLint et Prettier

npm install -D \
  eslint @eslint/js typescript-eslint \
  prettier eslint-config-prettier

# eslint.config.js
cat > eslint.config.js << 'EOF'
import js from '@eslint/js';
import ts from 'typescript-eslint';
import prettier from 'eslint-config-prettier';

export default [
  js.configs.recommended,
  ...ts.configs.recommended,
  prettier,
  {
    rules: {
      '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
      '@typescript-eslint/no-explicit-any': 'warn',
      'no-console': ['warn', { allow: ['warn', 'error'] }]
    }
  }
];
EOF

# Lancer la vérification
npx eslint . --ext .ts,.tsx

Étape 12 — Scripts package.json utiles

{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "typecheck": "tsc --noEmit",
    "lint": "eslint . --ext .ts,.tsx",
    "format": "prettier --write 'src/**/*.{ts,tsx}'",
    "test": "vitest run"
  }
}

Le script typecheck est crucial : à chaque commit (via Husky et lint-staged), vous garantissez que le code en production compile sans erreur. Cela bloque 90% des bugs avant qu’ils n’atteignent vos clients.

Étape 13 — Pitfalls et bonnes pratiques

// MAUVAIS : as pour forcer un type sans vérification
const utilisateur = data as User; // dangereux si data invalide

// BON : validation avec type guard
function estUser(x: unknown): x is User {
  return typeof x === 'object' && x !== null
    && 'id' in x && 'email' in x;
}
if (estUser(data)) {
  // ici TypeScript sait que data est User
}

// MAUVAIS : any partout
function traiter(x: any) { return x.toString(); }

// BON : générique ou unknown
function traiter<T>(x: T): string { return String(x); }

Erreurs fréquentes

  • Cannot find module : oubli d’installer @types/xxx pour une dépendance JS sans types intégrés. Solution : npm install -D @types/lodash par exemple.
  • Object is possibly undefined : activez noUncheckedIndexedAccess dans tsconfig pour forcer la vérification, et utilisez ?. ou un check explicite.
  • Mode strict trop dur d’un coup : activez les options progressivement. Démarrez sans strict, puis strictNullChecks, puis noImplicitAny.
  • Mélanger import/require : en TypeScript moderne, utilisez exclusivement import. Le require ne marche que si esModuleInterop est désactivé.
  • Builder à la main avec tsc en production : préférez esbuild, swc ou tsx qui sont 10 à 100 fois plus rapides pour les gros projets.

Checklist de validation

  • TypeScript installé en dépendance dev avec npm install -D typescript
  • tsconfig.json créé avec strict: true
  • Au moins 5 fichiers .js renommés en .ts et compilés sans erreur
  • Une interface ou type alias défini pour le modèle métier principal
  • Un type union utilisé (ex: ModePaiement) à la place d’une chaîne libre
  • Une fonction générique écrite et testée
  • Validation runtime avec Zod pour au moins un endpoint API
  • ESLint configuré avec typescript-eslint et zero erreur
  • Script npm run typecheck dans package.json et exécuté avec succès
  • VS Code montre l’autocomplétion intelligente sur tous les fichiers .ts
  • Documentation interne mise à jour avec les conventions de typage
  • Premier déploiement réussi avec build TypeScript en CI/CD
Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité