ToolBox Hub

Meilleures Pratiques TypeScript 2026

Meilleures Pratiques TypeScript 2026

Les meilleures pratiques et patterns TypeScript pour écrire du code propre et maintenable.

16 mars 20268 min de lecture

Introduction : TypeScript, le standard du développement moderne

En 2026, TypeScript s'est imposé comme le langage de prédilection pour le développement web et au-delà. Avec plus de 90% des projets JavaScript professionnels qui utilisent TypeScript, la maîtrise de ses bonnes pratiques est devenue une compétence incontournable pour tout développeur front-end ou back-end.

TypeScript offre un système de types puissant qui permet de détecter les erreurs à la compilation plutôt qu'à l'exécution, améliore l'autocomplétion dans les IDE et sert de documentation vivante pour votre code. Cependant, mal utilisé, TypeScript peut devenir un frein à la productivité plutôt qu'un atout.

Ce guide présente les meilleures pratiques et patterns TypeScript en 2026, avec des exemples concrets et des explications détaillées.

Configuration optimale du tsconfig.json

Une bonne configuration TypeScript est la base d'un projet sain. Voici les options recommandées en 2026 :

{
  "compilerOptions": {
    "target": "ES2024",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Les points clés :

  • strict: true est non négociable. N'activez jamais strict: false sur un nouveau projet.
  • noUncheckedIndexedAccess force la vérification des accès par index, évitant les erreurs undefined silencieuses.
  • exactOptionalPropertyTypes distingue entre undefined explicite et l'absence de propriété.

Typage efficace

Préférez les interfaces pour les objets

// Bien : interface pour les formes d'objets
interface User {
  id: number;
  name: string;
  email: string;
  role: UserRole;
}

// Bien : type pour les unions, intersections et utilitaires
type UserRole = "admin" | "editor" | "viewer";
type UserWithPosts = User & { posts: Post[] };

Règle générale : utilisez interface pour les structures d'objets et type pour tout le reste (unions, intersections, types utilitaires, types conditionnels).

Évitez le type any

Le type any désactive complètement la vérification de types. Utilisez des alternatives plus sûres :

// Mauvais
function parseData(data: any): any {
  return data.items;
}

// Bien : utilisez unknown pour les données dont le type est inconnu
function parseData(data: unknown): Item[] {
  if (isValidResponse(data)) {
    return data.items;
  }
  throw new Error("Données invalides");
}

// Bien : utilisez un type guard pour affiner le type
function isValidResponse(data: unknown): data is ApiResponse {
  return (
    typeof data === "object" &&
    data !== null &&
    "items" in data &&
    Array.isArray((data as ApiResponse).items)
  );
}

Utilisez les types utilitaires natifs

TypeScript fournit de nombreux types utilitaires intégrés. Maîtrisez-les :

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
}

// Partial : toutes les propriétés deviennent optionnelles
type UserUpdate = Partial<User>;

// Pick : sélectionner certaines propriétés
type UserPublic = Pick<User, "id" | "name" | "email">;

// Omit : exclure certaines propriétés
type UserWithoutPassword = Omit<User, "password">;

// Required : toutes les propriétés deviennent obligatoires
type UserRequired = Required<User>;

// Record : créer un type avec des clés et valeurs typées
type UserPermissions = Record<UserRole, string[]>;

// ReturnType : extraire le type de retour d'une fonction
type ApiResult = ReturnType<typeof fetchUsers>;

// Awaited : extraire le type d'une Promise
type UserData = Awaited<ReturnType<typeof fetchUsers>>;

Patterns avancés

Discriminated Unions (Unions discriminées)

Ce pattern est essentiel pour modéliser des états distincts de manière type-safe :

// Définir les états possibles d'une requête
type RequestState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: Error };

// Utilisation avec narrowing automatique
function handleState(state: RequestState<User[]>) {
  switch (state.status) {
    case "idle":
      return "En attente...";
    case "loading":
      return "Chargement...";
    case "success":
      // TypeScript sait que state.data existe ici
      return `${state.data.length} utilisateurs trouvés`;
    case "error":
      // TypeScript sait que state.error existe ici
      return `Erreur : ${state.error.message}`;
  }
}

Branded Types (Types marqués)

Pour éviter de confondre des types structurellement identiques mais sémantiquement différents :

// Créer des types nominaux
type UserId = number & { readonly __brand: "UserId" };
type PostId = number & { readonly __brand: "PostId" };

// Fonctions de création
function createUserId(id: number): UserId {
  return id as UserId;
}

function createPostId(id: number): PostId {
  return id as PostId;
}

// Impossible de passer un UserId là où un PostId est attendu
function getPost(postId: PostId): Post {
  // ...
}

const userId = createUserId(1);
const postId = createPostId(1);

// getPost(userId); // Erreur de compilation !
getPost(postId); // OK

Const Assertions et Satisfies

// as const rend les valeurs en lecture seule et affine les types
const COLORS = {
  primary: "#3B82F6",
  secondary: "#10B981",
  danger: "#EF4444",
} as const;

// Le type est { readonly primary: "#3B82F6"; ... }
// au lieu de { primary: string; ... }

// satisfies vérifie le type sans l'élargir
const config = {
  apiUrl: "https://api.exemple.com",
  timeout: 5000,
  retries: 3,
} satisfies Record<string, string | number>;

// config.apiUrl est toujours de type string (pas string | number)

Gestion des erreurs

Le pattern Result

Évitez les exceptions pour le flux de contrôle. Utilisez le pattern Result :

type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };

async function fetchUser(id: number): Promise<Result<User>> {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      return {
        success: false,
        error: new Error(`HTTP ${response.status}`),
      };
    }
    const data = await response.json();
    return { success: true, data };
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error : new Error(String(error)),
    };
  }
}

// Utilisation propre et type-safe
const result = await fetchUser(123);
if (result.success) {
  console.log(result.data.name); // TypeScript connaît le type
} else {
  console.error(result.error.message);
}

Validation avec Zod

En 2026, Zod est le standard pour la validation de schémas en TypeScript :

import { z } from "zod";

// Définir le schéma
const UserSchema = z.object({
  id: z.number().positive(),
  name: z.string().min(2).max(100),
  email: z.string().email(),
  age: z.number().min(13).max(150).optional(),
  role: z.enum(["admin", "editor", "viewer"]),
});

// Extraire le type automatiquement du schéma
type User = z.infer<typeof UserSchema>;

// Valider les données entrantes
function processUser(input: unknown): User {
  return UserSchema.parse(input); // Lance une erreur si invalide
}

// Validation douce (sans exception)
const result = UserSchema.safeParse(input);
if (result.success) {
  console.log(result.data); // Type User
} else {
  console.error(result.error.issues);
}

Bonnes pratiques pour les fonctions

Surcharges de fonctions

// Surcharges pour des types de retour différents selon les arguments
function createElement(tag: "div"): HTMLDivElement;
function createElement(tag: "span"): HTMLSpanElement;
function createElement(tag: "input"): HTMLInputElement;
function createElement(tag: string): HTMLElement {
  return document.createElement(tag);
}

const div = createElement("div"); // Type : HTMLDivElement

Fonctions génériques avec contraintes

// Générique avec contrainte
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

// Générique avec valeur par défaut
function createList<T = string>(items: T[]): { items: T[]; count: number } {
  return { items, count: items.length };
}

// Générique avec inférence
function groupBy<T, K extends string>(
  items: T[],
  keyFn: (item: T) => K
): Record<K, T[]> {
  return items.reduce(
    (acc, item) => {
      const key = keyFn(item);
      acc[key] = acc[key] || [];
      acc[key].push(item);
      return acc;
    },
    {} as Record<K, T[]>
  );
}

Organisation du code

Barrel exports

Organisez vos exports avec des fichiers index :

// src/models/index.ts
export type { User } from "./user";
export type { Post } from "./post";
export type { Comment } from "./comment";
export { UserSchema, PostSchema } from "./schemas";

Conventions de nommage

ÉlémentConventionExemple
Variables et fonctionscamelCasegetUserById
Classes et interfacesPascalCaseUserService
TypesPascalCaseApiResponse
ConstantesSCREAMING_SNAKE_CASEMAX_RETRY_COUNT
Fichierskebab-caseuser-service.ts
GénériquesT, K, V ou descriptifTResponse, TKey

Performances et optimisation

Utilisez readonly pour l'immutabilité

// Paramètres en lecture seule
function processItems(items: readonly string[]): number {
  // items.push("new"); // Erreur !
  return items.length;
}

// Objets profondément en lecture seule
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

Évitez les re-renders avec les types corrects

En React avec TypeScript, typez correctement vos props :

interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: "primary" | "secondary" | "danger";
  disabled?: boolean;
  children?: React.ReactNode;
}

const Button: React.FC<ButtonProps> = ({
  label,
  onClick,
  variant = "primary",
  disabled = false,
  children,
}) => {
  return (
    <button
      className={`btn btn-${variant}`}
      onClick={onClick}
      disabled={disabled}
      aria-label={label}
    >
      {children || label}
    </button>
  );
};

Conclusion

TypeScript est un outil extraordinaire lorsqu'il est bien utilisé. Les pratiques présentées dans ce guide vous permettront d'écrire du code plus sûr, plus lisible et plus maintenable. L'investissement dans l'apprentissage du système de types de TypeScript est toujours rentable à long terme.

Pour valider et formater vos structures de données TypeScript, utilisez le formateur JSON de ToolBox Hub. Et pour tester vos expressions régulières TypeScript, le testeur de regex est un outil indispensable.

Rappelez-vous : le meilleur code TypeScript est celui qui tire parti du compilateur comme d'un assistant qui vous aide à éviter les erreurs avant même d'exécuter votre code.

Articles associés