Vous développez en JavaScript depuis des années et vous vous demandez si TypeScript vaut vraiment le coup ? La réponse courte : absolument. Mais migrer un projet existant peut sembler intimidant, surtout quand on parle d’une codebase de plusieurs milliers de lignes accumulées au fil des années.
La bonne nouvelle, c’est que TypeScript a été conçu précisément pour faciliter cette transition. Contrairement à un changement de framework qui nécessite une réécriture complète, TypeScript est un sur-ensemble de JavaScript. Votre code JavaScript valide est déjà du TypeScript valide. Vous pouvez migrer fichier par fichier, fonction par fonction, sans tout casser d’un coup.
Cette migration progressive présente des avantages concrets et mesurables : moins de bugs en production, refactoring plus sûr, meilleure documentation implicite du code, et une expérience développeur considérablement améliorée grâce à l’autocomplétion intelligente. Des entreprises comme Airbnb ont rapporté une réduction de 38% des bugs après migration vers TypeScript.
Ce guide vous accompagne étape par étape dans cette transition. Nous commençons par configurer TypeScript dans votre projet existant, puis nous explorons les stratégies de migration pragmatiques qui vous permettront d’adopter TypeScript à votre rythme, sans pression, sans risque.
Table of Contents
Pourquoi migrer vers TypeScript en 2025 ?
Avant de plonger dans le « comment », clarifions le « pourquoi ». TypeScript n’est pas juste une mode passagère. En 2025, il s’est imposé comme le standard de facto pour les projets JavaScript professionnels.
Les bénéfices concrets et mesurables
Détection d’erreurs avant l’exécution
Le compilateur TypeScript attrape les erreurs de typage avant même que vous lanciez votre code. Une faute de frappe dans un nom de propriété ? TypeScript vous alerte immédiatement dans votre éditeur. Un argument manquant dans un appel de fonction ? Le compilateur refuse de compiler.
Airbnb a publié une étude montrant que 38% de leurs bugs en production auraient pu être évités avec TypeScript. Cela représente des heures de debugging économisées et une meilleure expérience utilisateur.
Refactoring sûr et rapide
Renommer une propriété utilisée dans 50 fichiers différents ? Avec JavaScript pur, vous priez pour ne rien oublier. Avec TypeScript, vous renommez via votre IDE et tous les usages sont automatiquement mis à jour. Le compilateur vous prévient si un endroit a été oublié.
Cette capacité à refactorer en confiance change radicalement la maintenabilité des grands projets. Vous osez améliorer votre architecture parce que vous savez que TypeScript vous empêchera de casser quoi que ce soit.
Autocomplétion et IntelliSense avancés
Avec JavaScript, votre IDE devine plus ou moins ce que vous essayez de faire. Avec TypeScript, il sait exactement quelles propriétés existent sur un objet, quels arguments une fonction attend, et quel type elle retourne.
Cette amélioration de l’expérience développeur se traduit par une productivité accrue de 15-20% selon plusieurs études. Moins de temps à chercher dans la documentation, moins d’allers-retours entre fichiers pour comprendre une API.
Documentation vivante
Les types TypeScript servent de documentation qui ne peut jamais devenir obsolète. Quand vous modifiez une fonction, vous devez aussi mettre à jour sa signature de type, sinon ça ne compile pas. Fini les commentaires JSDoc qui datent de deux ans et qui ne correspondent plus à la réalité.
typescript
// La signature de type documente exactement ce que fait cette fonction
function calculateDiscount(
price: number,
discountPercentage: number,
maxDiscount?: number
): number {
// Implémentation
}
Un développeur qui découvre cette fonction comprend immédiatement ce qu’elle attend et ce qu’elle retourne, sans lire l’implémentation ni chercher des exemples d’utilisation.
L’écosystème TypeScript en 2025
Support universel des frameworks
Tous les frameworks modernes supportent TypeScript nativement ou le recommandent activement :
- React : Create React App, Next.js, Remix, Gatsby
- Vue : Vue 3 est écrit en TypeScript
- Angular : TypeScript par défaut depuis toujours
- Svelte : SvelteKit supporte TypeScript out-of-the-box
- Node.js : Support natif avec tsx et ts-node
Cette adoption massive signifie que vous trouverez des types pour pratiquement toutes les bibliothèques populaires via DefinitelyTyped.
Demande du marché
Une analyse de 50 000 offres d’emploi développeur JavaScript en 2024 montre que 72% mentionnent TypeScript comme compétence requise ou fortement recommandée. C’est désormais un atout indispensable sur le CV d’un développeur frontend ou fullstack.
Les entreprises qui recrutent savent que TypeScript réduit les bugs et améliore la maintenabilité. Maîtriser TypeScript ouvre des portes vers des projets plus ambitieux et mieux rémunérés.
Quand ne PAS migrer vers TypeScript ?
Soyons honnêtes : TypeScript n’est pas toujours la solution optimale.
Évitez TypeScript si :
- Vous travaillez sur un prototype ou MVP rapide qui sera probablement jeté dans 2 mois
- Votre équipe est très junior et découvre encore JavaScript (ajoutez TypeScript dans 6-12 mois)
- Votre projet fait moins de 1000 lignes et n’évoluera probablement plus
- Vous développez seul des scripts d’automatisation ponctuels
Migrez vers TypeScript si :
- Votre codebase dépasse 3000-5000 lignes
- Plusieurs développeurs travaillent sur le projet
- Le projet sera maintenu sur plusieurs années
- Vous passez beaucoup de temps à debugger des erreurs de type
- Vous voulez faciliter l’onboarding de nouveaux développeurs
Les prérequis avant de commencer
Avant de lancer la migration, assurez-vous d’avoir une base solide.
Maîtrise de JavaScript moderne
TypeScript ajoute des types au-dessus de JavaScript. Si vous n’êtes pas à l’aise avec ES6+ (destructuring, spread operator, arrow functions, promises, async/await), commencez par consolider ces bases.
Revoyez les concepts JavaScript fondamentaux :
- Les fonctions fléchées
- Les promesses
- Les méthodes de tableau
- La manipulation du DOM si vous faites du frontend
Tests automatisés en place
Si votre projet n’a pas de tests, c’est le moment parfait pour en ajouter avant de migrer. Les tests vous serviront de filet de sécurité pendant la migration. Vous pourrez vérifier que votre comportement n’a pas changé après avoir ajouté des types.
Un minimum syndical :
- Tests unitaires sur la logique métier critique
- Tests d’intégration sur les flows principaux
- Tests E2E sur les parcours utilisateurs clés
Configuration Git propre
Créez une branche dédiée pour la migration. Committez fréquemment, fichier par fichier. Si quelque chose casse, vous pourrez facilement revenir en arrière.
bash
git checkout -b migration/typescript
Configurez également un .gitignore pour les fichiers TypeScript générés :
# TypeScript
*.js.map
*.d.ts
dist/
build/
Node.js et npm à jour
TypeScript nécessite Node.js. Assurez-vous d’avoir au minimum Node.js 18+ installé. Vérifiez votre version :
bash
node --version
npm --version
Si nécessaire, mettez à jour via nvm ou téléchargez la dernière version LTS depuis nodejs.org.
Configuration initiale de TypeScript
Installons et configurons TypeScript dans votre projet existant.
Installation de TypeScript
Dans votre projet, installez TypeScript comme dépendance de développement :
bash
npm install --save-dev typescript
Vérifiez que l’installation a réussi :
bash
npx tsc --version
Vous devriez voir quelque chose comme Version 5.3.3 (ou plus récent).
Créer le fichier tsconfig.json
Le tsconfig.json configure le comportement du compilateur TypeScript. Générez un fichier de base :
bash
npx tsc --init
Cette commande crée un tsconfig.json avec beaucoup d’options commentées. Voici une configuration recommandée pour une migration progressive :
json
{
"compilerOptions": {
// Cible de compilation : ES2020 fonctionne dans tous les navigateurs modernes
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
// Options strictes (à activer progressivement)
"strict": false,
"noImplicitAny": false,
"strictNullChecks": false,
// Permet JavaScript et TypeScript côte à côte
"allowJs": true,
"checkJs": false,
// Génération des fichiers
"outDir": "./dist",
"rootDir": "./src",
// Support des modules
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
// Options de qualité de code
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
// Source maps pour le debugging
"sourceMap": true,
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.spec.ts"]
}
Points clés de cette configuration :
allowJs: true: permet de mixer fichiers.jset.tspendant la migrationstrict: false: démarre avec TypeScript en mode permissif, on durcira progressivementnoImplicitAny: false: autorise le typeanyimplicite au débutcheckJs: false: ne vérifie pas encore les fichiers JavaScript
Ajuster les scripts npm
Modifiez votre package.json pour ajouter des commandes TypeScript :
json
{
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"type-check": "tsc --noEmit",
"dev": "your-existing-dev-command"
}
}
npm run build: compile TypeScript vers JavaScriptnpm run build:watch: recompile automatiquement à chaque changementnpm run type-check: vérifie les types sans générer de fichiers (utile en CI/CD)
Premier test de compilation
Créez un fichier de test src/test.ts :
typescript
const greeting: string = "Hello TypeScript!";
console.log(greeting);
Compilez :
bash
npm run build
Si tout fonctionne, vous devriez voir un fichier dist/test.js généré. Félicitations, TypeScript est opérationnel !
Stratégie de migration progressive
La migration d’un projet entier ne se fait pas en une journée. Voici une stratégie éprouvée pour migrer progressivement sans casser votre production.
Phase 1 : Coexistence JavaScript et TypeScript
Objectif : faire fonctionner TypeScript à côté de votre code JavaScript existant sans rien casser.
- Configurez
tsconfig.jsonavecallowJs: trueetstrict: false - Renommez UN seul fichier simple
.js→.ts - Corrigez les erreurs TypeScript dans ce fichier
- Testez que tout fonctionne encore
- Committez
Répétez ce processus fichier par fichier. Commencez par les fichiers utilitaires (utils, helpers) qui ont peu de dépendances. Évitez les gros fichiers complexes au début.
Phase 2 : Migration des fichiers critiques
Objectif : migrer les fichiers qui apportent le plus de valeur quand typés.
Priorisez dans cet ordre :
- Modèles de données : vos interfaces/types métier
- API clients : fonctions qui font des calls HTTP
- Stores/State management : Redux, Zustand, Context API
- Composants réutilisables : buttons, inputs, cards
- Logique métier : calculs, validations, transformations de données
Ces fichiers bénéficient le plus du typage car ils sont utilisés partout dans l’application.
Phase 3 : Activation progressive du mode strict
Objectif : durcir progressivement les règles TypeScript pour maximiser les bénéfices.
Une fois que 50-70% de votre code est en TypeScript, commencez à activer les options strictes une par une dans tsconfig.json :
- Activez
noImplicitAny: true→ forcez à typer les paramètres de fonctions - Corrigez les erreurs qui apparaissent
- Activez
strictNullChecks: true→ forcez à gérer null/undefined - Corrigez les erreurs
- Activez
strict: true→ active toutes les vérifications strictes - Corrigez les dernières erreurs
Cette approche incrémentale évite d’avoir 500 erreurs d’un coup qui vous découragent.
Phase 4 : Désactivation de allowJs
Objectif : forcer la migration complète.
Quand il ne reste que quelques fichiers JavaScript, désactivez allowJs dans tsconfig.json. Le compilateur refusera maintenant de compiler tant que des fichiers .js existent (hors node_modules).
Cela crée une pression saine pour finaliser la migration. Fixez une deadline réaliste et migrez les derniers fichiers.
Combien de temps prévoir ?
La durée dépend évidemment de la taille de votre projet :
Taille du projetTemps de migration estimé< 5 000 lignes1-2 semaines5 000 - 20 000 lignes1-2 mois20 000 - 50 000 lignes2-4 mois> 50 000 lignes4-12 mois
Ces estimations supposent qu’un développeur consacre 20-30% de son temps à la migration en parallèle des développements features.
Convertir votre premier fichier
Prenons un exemple concret pour voir comment convertir un fichier JavaScript en TypeScript.
Fichier JavaScript original
javascript
// utils/user.js
export function formatUserName(user) {
if (!user) return "Anonymous";
return `${user.firstName} ${user.lastName}`.trim();
}
export function isAdminUser(user) {
return user.role === "admin" || user.permissions.includes("admin");
}
export function getUserAge(user) {
const today = new Date();
const birthDate = new Date(user.birthDate);
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age;
}
Étape 1 : Renommer en .ts
Renommez simplement utils/user.js → utils/user.ts.
Lancez la compilation :
bash
npm run type-check
TypeScript va immédiatement identifier des problèmes potentiels.
Étape 2 : Définir les interfaces
Créez un fichier types/user.ts pour vos types :
typescript
// types/user.ts
export interface User {
id: string;
firstName: string;
lastName: string;
birthDate: string; // ISO date string
role: "user" | "admin" | "moderator";
permissions: string[];
}
Étape 3 : Typer les fonctions
Modifiez votre fichier avec les types appropriés :
typescript
// utils/user.ts
import { User } from '../types/user';
export function formatUserName(user: User | null | undefined): string {
if (!user) return "Anonymous";
return `${user.firstName} ${user.lastName}`.trim();
}
export function isAdminUser(user: User): boolean {
return user.role === "admin" || user.permissions.includes("admin");
}
export function getUserAge(user: User): number {
const today = new Date();
const birthDate = new Date(user.birthDate);
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age;
}
Étape 4 : Gérer les erreurs TypeScript
TypeScript peut maintenant vous signaler des problèmes que JavaScript ne voyait pas. Par exemple, si ailleurs dans votre code vous appelez :
typescript
formatUserName(null) // OK, géré
isAdminUser(null) // ❌ Erreur : user peut être null
Vous devez corriger l’appelant ou modifier la fonction pour accepter null :
typescript
export function isAdminUser(user: User | null): boolean {
if (!user) return false;
return user.role === "admin" || user.permissions.includes("admin");
}
Étape 5 : Tester et valider
Lancez vos tests :
bash
npm test
Si tout passe, committez :
bash
git add src/utils/user.ts src/types/user.ts
git commit -m "Migrer utils/user vers TypeScript"
Félicitations, vous avez converti votre premier fichier ! Répétez ce processus pour les autres fichiers.
Gérer les types dans les dépendances tierces
Une des beautés de TypeScript, c’est que la plupart des bibliothèques populaires fournissent déjà des types.
Bibliothèques avec types intégrés
Certaines librairies sont écrites en TypeScript et incluent leurs types :
- React (depuis v18)
- Vue 3
- Express (via @types/express)
- Axios
- date-fns
Quand vous installez ces packages, les types viennent automatiquement. Votre IDE vous offre l’autocomplétion immédiatement.
DefinitelyTyped : types pour les packages JavaScript
Pour les packages JavaScript qui n’incluent pas de types, la communauté maintient DefinitelyTyped, un dépôt centralisé de définitions de types.
Si vous utilisez lodash :
bash
npm install lodash
npm install --save-dev @types/lodash
Le package @types/lodash contient les définitions de types pour lodash. Maintenant TypeScript connaît tous les types de lodash.
Recherchez les types disponibles sur https://www.npmjs.com/search?q=%40types.
Bibliothèques sans types disponibles
Que faire si une bibliothèque n’a pas de types et n’existe pas sur DefinitelyTyped ?
Option 1 : Déclarer le module comme any
Créez un fichier src/types/external.d.ts :
typescript
// Déclare que ce module existe mais sans types précis
declare module 'obscure-library' {
const content: any;
export default content;
}
Vous perdez les bénéfices du typage pour cette bibliothèque, mais au moins votre projet compile.
Option 2 : Créer vos propres types
Si vous utilisez intensivement cette bibliothèque, investissez du temps pour créer des types partiels :
typescript
declare module 'obscure-library' {
export interface Config {
apiKey: string;
timeout?: number;
}
export function initialize(config: Config): void;
export function fetchData(id: string): Promise<any>;
}
Vous ne typez que ce que vous utilisez réellement. C’est souvent suffisant.
Option 3 : Contribuer à DefinitelyTyped
Si la bibliothèque est populaire, créez une PR sur DefinitelyTyped pour partager vos types avec la communauté. C’est valorisant pour votre profil GitHub et utile pour des milliers de développeurs.
Patterns TypeScript essentiels
Maintenant que vous migrez, adoptons les patterns TypeScript qui feront briller votre code.
Interfaces vs Types
TypeScript offre deux syntaxes pour définir des types : interface et type. Quelle différence ?
Interfaces :
typescript
interface User {
id: string;
name: string;
}
// Les interfaces peuvent s'étendre
interface Admin extends User {
permissions: string[];
}
// Les interfaces peuvent être fusionnées (declaration merging)
interface User {
email: string; // S'ajoute à la définition précédente
}
Types :
typescript
type User = {
id: string;
name: string;
};
// Les types supportent les unions et intersections
type Role = "user" | "admin" | "guest";
type Admin = User & {
permissions: string[];
};
Règle pratique : utilisez interface pour les objets et les classes. Utilisez type pour les unions, les tuples, et les types utilitaires complexes.
Utility Types intégrés
TypeScript fournit des types utilitaires puissants :
Partial<T> : rend toutes les propriétés optionnelles
typescript
interface User {
id: string;
name: string;
email: string;
}
// Utile pour les mises à jour partielles
function updateUser(id: string, updates: Partial<User>) {
// updates peut contenir n'importe quelle combinaison de propriétés User
}
updateUser("123", { name: "John" }); // OK
updateUser("123", { email: "john@example.com" }); // OK
Pick<T, K> : sélectionne certaines propriétés
typescript
type UserPreview = Pick<User, 'id' | 'name'>;
// Équivalent à : { id: string; name: string; }
Omit<T, K> : exclut certaines propriétés
typescript
type UserWithoutEmail = Omit<User, 'email'>;
// Équivalent à : { id: string; name: string; }
Record<K, T> : crée un objet avec des clés typées
typescript
type UserRoles = Record<string, "admin" | "user">;
const roles: UserRoles = {
"user1": "admin",
"user2": "user"
};
Génériques (Generics)
Les génériques permettent de créer des fonctions et composants réutilisables avec différents types.
typescript
// Sans générique : fonction limitée à un type
function getFirstItemString(array: string[]): string {
return array[0];
}
// Avec générique : fonction qui fonctionne avec n'importe quel type
function getFirstItem<T>(array: T[]): T {
return array[0];
}
const firstNumber = getFirstItem([1, 2, 3]); // type: number
const firstString = getFirstItem(["a", "b", "c"]); // type: string
Les génériques brillent vraiment avec les APIs :
typescript
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
return response.json();
}
// TypeScript infère automatiquement le type de data
const userResponse = await fetchApi<User>("/api/user/123");
console.log(userResponse.data.name); // TypeScript sait que data est un User
Type Guards
Les type guards permettent d’affiner les types dans des conditions :
typescript
interface Dog {
bark(): void;
}
interface Cat {
meow(): void;
}
type Pet = Dog | Cat;
// Type guard personnalisé
function isDog(pet: Pet): pet is Dog {
return (pet as Dog).bark !== undefined;
}
function makeSound(pet: Pet) {
if (isDog(pet)) {
pet.bark(); // TypeScript sait que pet est un Dog ici
} else {
pet.meow(); // TypeScript sait que pet est un Cat ici
}
}
Enums vs Union Types
Pour les constantes, vous avez le choix entre enum et union types :
typescript
// Enum (génère du JavaScript)
enum Status {
Pending = "pending",
Active = "active",
Archived = "archived"
}
// Union type (pur TypeScript, pas de JS généré)
type Status = "pending" | "active" | "archived";
Recommandation : préférez les union types. Ils sont plus simples, n’ajoutent pas de code JavaScript, et fonctionnent mieux avec les types utilitaires.
Pièges courants et comment les éviter
Tous les développeurs tombent dans ces pièges lors de leur migration. Anticipez-les.
Piège #1 : Ignorer strictNullChecks
Sans strictNullChecks, TypeScript ne distingue pas string de string | null | undefined. Cela cache des bugs potentiels.
typescript
// Sans strictNullChecks
function getUserName(user: User): string {
return user.name.toUpperCase(); // Peut crasher si user.name est null
}
Solution : activez strictNullChecks et gérez explicitement null/undefined :
typescript
// Avec strictNullChecks
function getUserName(user: User): string {
return user.name?.toUpperCase() ?? "Anonymous";
}
Piège #2 : Type assertions dangereuses
Les assertions de type (as) forcent TypeScript à accepter votre version, même si elle est fausse.
typescript
// ❌ Dangereux : si data n'est pas vraiment un User, boom en production
const user = data as User;
console.log(user.name.toUpperCase());
Solution : utilisez les type guards et validez vraiment les données :
typescript
// ✅ Sûr : validation avant utilisation
function isUser(data: unknown): data is User {
return (
typeof data === "object" &&
data !== null &&
"name" in data &&
typeof (data as User).name === "string"
);
}
if (isUser(data)) {
console.log(data.name.toUpperCase()); // Sûr maintenant
}
Piège #4 : Oublier les types de retour explicites
TypeScript infère les types de retour, mais les déclarer explicitement améliore la maintenabilité :
typescript
// ❌ Type de retour inféré, pas clair
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// ✅ Type de retour explicite, documentation claire
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
Les types de retour explicites empêchent aussi les régressions : si vous modifiez l’implémentation et changez accidentellement le type retourné, TypeScript vous alertera.
Piège #5 : Types trop complexes
Les développeurs débutants en TypeScript ont tendance à créer des types sur-ingéniérés :
typescript
// ❌ Trop complexe pour ce que ça fait
type ApiResponse<T, E = Error> = {
data?: T extends Array<infer U> ? U[] : T;
error?: E;
metadata: {
timestamp: number;
version: string;
};
} & (
| { status: 'success'; data: T }
| { status: 'error'; error: E }
);
Solution : gardez vos types simples et lisibles. Si un type devient trop complexe, décomposez-le :
typescript
// ✅ Simple et clair
interface SuccessResponse<T> {
status: 'success';
data: T;
metadata: Metadata;
}
interface ErrorResponse {
status: 'error';
error: Error;
metadata: Metadata;
}
type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;
Piège #6 : Ne pas typer les événements
Quand vous gérez des événements DOM, typez-les correctement pour accéder aux propriétés spécifiques :
typescript
// ❌ Type générique, perd les propriétés spécifiques
function handleSubmit(e: Event) {
e.preventDefault();
const form = e.target; // Type: EventTarget | null
// form.elements n'existe pas sur EventTarget
}
// ✅ Type spécifique pour accéder aux propriétés du formulaire
function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
const form = e.currentTarget; // Type: HTMLFormElement
const data = new FormData(form); // Maintenant ça marche !
}
Migration d’un projet React
React et TypeScript forment un couple parfait. Voici comment migrer votre application React existante.
Installation des types React
Si ce n’est pas déjà fait, installez les types pour React :
bash
npm install --save-dev @types/react @types/react-dom
Pour React Router, si vous l’utilisez :
bash
npm install --save-dev @types/react-router-dom
Convertir un composant fonctionnel
Avant (JavaScript) :
jsx
// Button.jsx
import React from 'react';
export function Button({ children, onClick, disabled, variant = 'primary' }) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
);
}
Après (TypeScript) :
tsx
// Button.tsx
import React from 'react';
interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
variant?: 'primary' | 'secondary' | 'danger';
}
export function Button({
children,
onClick,
disabled = false,
variant = 'primary'
}: ButtonProps) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
);
}
Améliorations apportées :
- Interface
ButtonPropsdocumente clairement les props acceptées variantutilise un union type pour limiter aux valeurs validesReact.ReactNodetype correctement children (peut être string, JSX, array, etc.)- Les props optionnelles sont marquées avec
?
Typer les hooks useState et useEffect
useState avec typage explicite :
tsx
import { useState } from 'react';
interface User {
id: string;
name: string;
email: string;
}
function UserProfile() {
// Type inféré automatiquement
const [count, setCount] = useState(0); // type: number
// Type explicite nécessaire quand initialValue est null
const [user, setUser] = useState<User | null>(null);
// Type pour tableau
const [users, setUsers] = useState<User[]>([]);
return (
<div>
{user && <p>{user.name}</p>}
</div>
);
}
useEffect typé :
tsx
import { useEffect } from 'react';
function DataFetcher() {
useEffect(() => {
// TypeScript vérifie que vous retournez bien une cleanup function ou void
const controller = new AbortController();
fetchData(controller.signal)
.then(data => console.log(data))
.catch(err => console.error(err));
// Cleanup function correctement typée
return () => {
controller.abort();
};
}, []); // TypeScript vérifie les dépendances
return <div>Loading...</div>;
}
Typer les événements React
React a ses propres types d’événements qui étendent les événements DOM natifs :
tsx
import { ChangeEvent, FormEvent, MouseEvent } from 'react';
function Form() {
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value); // TypeScript connaît target.value
};
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
};
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
console.log(e.clientX, e.clientY);
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleInputChange} />
<button onClick={handleClick}>Submit</button>
</form>
);
}
Typer useContext
Créez un contexte typé pour éviter les problèmes de null :
tsx
import { createContext, useContext, useState, ReactNode } from 'react';
interface AuthContextType {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
// Créer le contexte avec undefined par défaut
const AuthContext = createContext<AuthContextType | undefined>(undefined);
// Hook personnalisé qui force la présence du contexte
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
// Provider typé
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = async (email: string, password: string) => {
const user = await apiLogin(email, password);
setUser(user);
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
Composants génériques
Pour créer des composants vraiment réutilisables, utilisez les génériques :
tsx
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>
{renderItem(item)}
</li>
))}
</ul>
);
}
// Utilisation avec typage automatique
<List
items={users}
renderItem={user => <span>{user.name}</span>}
keyExtractor={user => user.id}
/>
Migration de Redux avec TypeScript
Si vous utilisez Redux, TypeScript améliore drastiquement l’expérience :
typescript
// store/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface UserState {
currentUser: User | null;
loading: boolean;
error: string | null;
}
const initialState: UserState = {
currentUser: null,
loading: false,
error: null
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setUser: (state, action: PayloadAction<User>) => {
state.currentUser = action.payload;
},
setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload;
},
setError: (state, action: PayloadAction<string>) => {
state.error = action.payload;
}
}
});
export const { setUser, setLoading, setError } = userSlice.actions;
export default userSlice.reducer;
// Types pour useSelector
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Avec ces types, vos hooks Redux sont complètement typés :
tsx
import { useSelector, useDispatch } from 'react-redux';
import type { RootState, AppDispatch } from './store';
function UserProfile() {
// TypeScript connaît la structure du state
const user = useSelector((state: RootState) => state.user.currentUser);
const dispatch = useDispatch<AppDispatch>();
return <div>{user?.name}</div>;
}
Pour approfondir React et TypeScript, consultez notre guide sur les techniques avancées React.
Migration d’un projet Node.js/Express
Côté backend, TypeScript apporte les mêmes bénéfices : moins de bugs, meilleure maintenabilité.
Configuration spécifique Node.js
Ajustez votre tsconfig.json pour Node :
json
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"lib": ["ES2022"],
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
Installation des types pour Node et Express
bash
npm install --save-dev @types/node @types/express
Typer les routes Express
Avant (JavaScript) :
javascript
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/:id', async (req, res) => {
try {
const user = await getUserById(req.params.id);
res.json(user);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.post('/', async (req, res) => {
try {
const newUser = await createUser(req.body);
res.status(201).json(newUser);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
module.exports = router;
Après (TypeScript) :
typescript
// routes/users.ts
import { Router, Request, Response } from 'express';
import { getUserById, createUser } from '../services/userService';
import { User, CreateUserDto } from '../types/user';
const router = Router();
// Typer les paramètres de route
interface UserParams {
id: string;
}
router.get('/:id', async (req: Request<UserParams>, res: Response) => {
try {
const user = await getUserById(req.params.id);
res.json(user);
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
res.status(500).json({ error: message });
}
});
// Typer le body de la requête
router.post('/', async (req: Request<{}, {}, CreateUserDto>, res: Response) => {
try {
const newUser = await createUser(req.body);
res.status(201).json(newUser);
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
res.status(400).json({ error: message });
}
});
export default router;
Typer les middlewares
Les middlewares Express ont une signature spécifique :
typescript
import { Request, Response, NextFunction } from 'express';
// Middleware simple
function logger(req: Request, res: Response, next: NextFunction) {
console.log(`${req.method} ${req.path}`);
next();
}
// Middleware avec gestion d'erreur
function errorHandler(
err: Error,
req: Request,
res: Response,
next: NextFunction
) {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
}
// Middleware d'authentification avec extension de Request
declare global {
namespace Express {
interface Request {
user?: User;
}
}
}
function authenticate(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
res.status(401).json({ error: 'No token provided' });
return;
}
try {
const decoded = verifyToken(token);
req.user = decoded; // TypeScript connaît maintenant req.user
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
}
Typer les services et repositories
Créez des interfaces claires pour vos services :
typescript
// services/userService.ts
import { User, CreateUserDto, UpdateUserDto } from '../types/user';
import { UserRepository } from '../repositories/userRepository';
export class UserService {
constructor(private userRepository: UserRepository) {}
async getById(id: string): Promise<User> {
const user = await this.userRepository.findById(id);
if (!user) {
throw new Error('User not found');
}
return user;
}
async create(data: CreateUserDto): Promise<User> {
// Validation
if (!data.email || !data.password) {
throw new Error('Email and password are required');
}
return this.userRepository.create(data);
}
async update(id: string, data: UpdateUserDto): Promise<User> {
const user = await this.getById(id);
return this.userRepository.update(id, data);
}
async delete(id: string): Promise<void> {
await this.getById(id); // Vérifie que l'utilisateur existe
await this.userRepository.delete(id);
}
}
Typer Mongoose (MongoDB)
Si vous utilisez MongoDB avec Mongoose :
typescript
import { Schema, model, Document } from 'mongoose';
// Interface pour le document TypeScript
export interface IUser {
email: string;
firstName: string;
lastName: string;
password: string;
createdAt: Date;
}
// Interface pour le document Mongoose (inclut les méthodes)
export interface IUserDocument extends IUser, Document {
fullName(): string;
}
// Schéma Mongoose
const userSchema = new Schema<IUserDocument>({
email: { type: String, required: true, unique: true },
firstName: { type: String, required: true },
lastName: { type: String, required: true },
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now }
});
// Méthode d'instance
userSchema.methods.fullName = function(): string {
return `${this.firstName} ${this.lastName}`;
};
// Export du modèle typé
export const User = model<IUserDocument>('User', userSchema);
Utilisation :
typescript
const user = await User.findById(userId);
if (user) {
console.log(user.fullName()); // TypeScript connaît cette méthode
}
Validation avec Zod
Pour valider les données entrantes avec des types TypeScript inférés automatiquement, utilisez Zod :
typescript
import { z } from 'zod';
// Définir le schéma de validation
const createUserSchema = z.object({
email: z.string().email(),
firstName: z.string().min(2),
lastName: z.string().min(2),
password: z.string().min(8),
age: z.number().int().positive().optional()
});
// TypeScript infère automatiquement le type depuis le schéma !
type CreateUserDto = z.infer<typeof createUserSchema>;
// Utilisation dans une route
router.post('/', async (req: Request, res: Response) => {
try {
// Validation + parsing en une seule ligne
const userData = createUserSchema.parse(req.body);
// userData est maintenant typé comme CreateUserDto
const user = await createUser(userData);
res.status(201).json(user);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ errors: error.errors });
} else {
res.status(500).json({ error: 'Server error' });
}
}
});
Outils et ressources pour accélérer la migration
Ne réinventez pas la roue. Utilisez ces outils pour accélérer votre migration.
ts-migrate : outil automatisé
Airbnb a open-sourcé ts-migrate, un outil qui automatise une grande partie de la migration :
bash
npx ts-migrate migrate <dossier-source>
L’outil :
- Renomme
.js→.tsautomatiquement - Ajoute des types
anylà où nécessaire - Génère les
tsconfig.jsonde base - Corrige certains problèmes courants
Attention : l’output nécessite du travail manuel pour remplacer les any par de vrais types, mais ça donne une base solide.
TypeScript Error Translator
Extension VS Code qui traduit les erreurs TypeScript cryptiques en explications claires :
Installation : cherchez « TypeScript Error Translator » dans les extensions VS Code.
Total TypeScript (ressource d’apprentissage)
Site web avec des exercices interactifs pour maîtriser TypeScript : https://www.totaltypescript.com/
Particulièrement utile pour comprendre les génériques, les types conditionnels et les utilitaires avancés.
TypeScript Playground
Testez rapidement du code TypeScript dans le navigateur sans setup : https://www.typescriptlang.org/play
Pratique pour expérimenter avec les types avant de les intégrer dans votre projet.
Assistants IA pour accélérer
Les assistants de code IA comme GitHub Copilot ou Cursor excellent pour générer des types TypeScript. Décrivez votre structure de données en commentaire, et ils génèrent l’interface correspondante.
ESLint + TypeScript
Configurez ESLint avec le plugin TypeScript pour catcher encore plus de problèmes :
bash
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
Configuration .eslintrc.js :
javascript
module.exports = {
parser: '@typescript-eslint/parser',
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }]
}
};
Prettier pour le formatage
TypeScript fonctionne parfaitement avec Prettier :
bash
npm install --save-dev prettier
Configuration .prettierrc :
json
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2
}
FAQ
Dois-je migrer tous mes fichiers en une fois ?
Absolument pas ! C’est même déconseillé. Migrez progressivement, fichier par fichier. TypeScript supporte parfaitement la coexistence de fichiers .js et .ts grâce à l’option allowJs. Commencez par les fichiers utilitaires simples, puis étendez progressivement aux fichiers plus complexes.
Combien de temps prend une migration TypeScript ?
Cela dépend de la taille de votre projet. Pour 10 000 lignes de code, comptez 2-4 semaines à temps partiel (20-30% de votre temps). Pour 50 000 lignes, plutôt 2-4 mois. L’important est de ne pas précipiter : une migration bien faite vaut mieux qu’une migration rapide mais bâclée.
Mon projet utilise Webpack/Babel, est-ce compatible avec TypeScript ?
Oui, complètement. Webpack a un loader TypeScript (ts-loader ou babel-loader avec @babel/preset-typescript). Babel peut transpiler TypeScript depuis la version 7. La plupart des build tools modernes (Vite, Parcel, esbuild) supportent TypeScript nativement.
TypeScript ralentit-il la compilation ?
Le checking des types ajoute effectivement du temps de compilation, mais c’est généralement négligeable (quelques secondes). Pour les gros projets, utilisez tsc --incremental qui ne recompile que ce qui a changé. En développement, des outils comme esbuild ou swc compilent TypeScript ultra-rapidement en ignorant le type checking (faites-le en parallèle).
Puis-je utiliser TypeScript sans framework ?
Absolument ! TypeScript fonctionne avec du JavaScript vanilla. Vous pouvez l’utiliser pour du DOM manipulation, des scripts Node.js, ou n’importe quel code JavaScript. Le typage améliore n’importe quel projet, pas seulement les applications React/Angular/Vue.
Comment gérer les fichiers de configuration (webpack.config.js, etc.) ?
Vous pouvez les laisser en JavaScript ou les migrer. Pour webpack, vous pouvez utiliser webpack.config.ts si vous installez ts-node. Mais honnêtement, laisser les configs en JavaScript est parfaitement acceptable. Concentrez vos efforts sur le code applicatif.
Les performances d’exécution sont-elles affectées ?
Non. TypeScript est uniquement un outil de développement. Le code final généré est du JavaScript standard, avec les mêmes performances que si vous aviez écrit du JS directement. Le type checking ne se produit qu’à la compilation, pas à l’exécution.
Dois-je activer strict mode immédiatement ?
Non, commencez avec strict: false. Une fois que 50-70% de votre code est migré, activez progressivement les options strictes une par une : noImplicitAny, puis strictNullChecks, puis finalement strict: true. Cette approche incrémentale évite d’avoir 500 erreurs d’un coup.
Comment convaincre mon équipe/manager de migrer ?
Présentez les bénéfices concrets : réduction des bugs en production (38% selon Airbnb), temps de debugging économisé, meilleure documentation du code, onboarding facilité pour les nouveaux devs. Proposez une migration progressive sur plusieurs sprints sans bloquer les features. Montrez l’amélioration de l’autocomplétion en live. Les résultats parlent d’eux-mêmes.
Que faire avec le code legacy qu’on ne touchera plus jamais ?
Laissez-le en JavaScript. Ne perdez pas de temps à migrer du code qui fonctionne et ne sera jamais modifié. Concentrez vos efforts sur le code actif qui évolue régulièrement. Quand vous devrez le toucher, ce sera le bon moment pour le migrer.
TypeScript fonctionne-t-il bien avec les tests ?
Excellemment. Jest, Vitest, et Mocha supportent tous TypeScript. Vos tests bénéficient aussi du typage : vous attrapez des erreurs dans vos tests avant de les lancer. Configuration exemple pour Jest :
bash
npm install --save-dev ts-jest @types/jest
javascript
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
Puis-je utiliser des nouvelles features JavaScript avant qu’elles soient supportées ?
Oui ! TypeScript supporte souvent les features ECMAScript avant qu’elles n’arrivent dans les navigateurs. Par exemple, les décorateurs, les private fields, etc. Le compilateur transpile ensuite vers du JavaScript compatible avec vos cibles.
Comment maintenir les types à jour avec l’évolution du projet ?
Les types évoluent naturellement avec votre code. Quand vous modifiez une fonction, TypeScript vous force à mettre à jour sa signature. C’est justement l’avantage : impossible d’avoir des types obsolètes. Si vous oubliez quelque chose, le compilateur le détectera immédiatement.1 : Abuser du type any
typescript
// ❌ Mauvais : any désactive complètement TypeScript
function processData(data: any) {
return data.value.toFixed(2);
}
any annule tous les bénéfices de TypeScript. Utilisez-le uniquement en dernier recours et documentez pourquoi.
Solution : utilisez unknown pour les types vraiment inconnus :
typescript
// ✅ Bon : unknown force à vérifier avant d'utiliser
function processData(data: unknown) {
if (typeof data === "object" && data !== null && "value" in data) {
const obj = data as { value: number };
return obj.value.toFixed(2);
}
throw new Error("Invalid data format");
}
0 commentaire