Aller au contenu principal
Retour au blog

TypeScript : les bonnes pratiques que j'applique en 2025

4 min
Partager :
TypeScript : les bonnes pratiques que j'applique en 2025

Pourquoi TypeScript est devenu indispensable

J'ai longtemps résisté à TypeScript. "Trop verbeux", "ralentit le développement"... Je me trompais. Sur un projet e-commerce l'année dernière, un bug en production a coûté 3 jours de debugging. La cause ? Un undefined qui se baladait silencieusement dans le code. Avec TypeScript, le compilateur l'aurait attrapé en 2 secondes.

Depuis, c'est non-négociable : tous mes projets commencent en TypeScript strict. Voici les patterns qui font vraiment la différence.

1. Éviter any à tout prix

Le type any annule tous les bénéfices de TypeScript. Utilisez unknown si vous ne connaissez pas le type :

// ❌ Mauvais
function processData(data: any) {
  return data.value; // Aucune vérification
}

// ✅ Bon
function processData(data: unknown) {
  if (typeof data === "object" && data !== null && "value" in data) {
    return (data as { value: string }).value;
  }
  throw new Error("Invalid data");
}

2. Types utilitaires natifs

TypeScript offre des types utilitaires puissants. Maîtrisez-les :

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

// Rendre tous les champs optionnels
type PartialUser = Partial<User>;

// Sélectionner certains champs
type PublicUser = Pick<User, "id" | "name">;

// Exclure certains champs
type UserWithoutPassword = Omit<User, "password">;

// Rendre certains champs obligatoires
type RequiredUser = Required<User>;

3. Discriminated Unions pour les états

C'est probablement le pattern que j'utilise le plus. Avant, je gérais les états de chargement avec des booléens (isLoading, isError, hasData). Résultat : des états impossibles possibles (genre isLoading: true ET isError: true). Les Discriminated Unions règlent ça élégamment :

type ApiState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: string };

function handleState(state: ApiState<User>) {
  switch (state.status) {
    case "idle":
      return "En attente";
    case "loading":
      return "Chargement...";
    case "success":
      return `Bienvenue ${state.data.name}`; // TypeScript sait que data existe
    case "error":
      return `Erreur: ${state.error}`; // TypeScript sait que error existe
  }
}

4. Generics pour la réutilisabilité

Les generics permettent de créer des fonctions et composants réutilisables :

// Hook générique pour les API
function useApi<T>(url: string): ApiState<T> {
  const [state, setState] = useState<ApiState<T>>({ status: "idle" });

  useEffect(() => {
    setState({ status: "loading" });
    fetch(url)
      .then((res) => res.json())
      .then((data) => setState({ status: "success", data }))
      .catch((error) => setState({ status: "error", error: error.message }));
  }, [url]);

  return state;
}

// Utilisation avec inférence automatique
const userState = useApi<User>("/api/user");

5. as const pour les valeurs littérales

Préservez les types littéraux avec as const :

// Sans as const - type: string[]
const rolesBasic = ["admin", "user", "guest"];

// Avec as const - type: readonly ['admin', 'user', 'guest']
const roles = ["admin", "user", "guest"] as const;

// Créer un type à partir des valeurs
type Role = (typeof roles)[number]; // 'admin' | 'user' | 'guest'

6. Template Literal Types

Créez des types dynamiques basés sur des chaînes :

type EventName = "click" | "focus" | "blur";
type Handler = `on${Capitalize<EventName>}`; // 'onClick' | 'onFocus' | 'onBlur'

// Pour les routes API
type ApiRoute = `/api/${string}`;
const userRoute: ApiRoute = "/api/users"; // ✅
const invalidRoute: ApiRoute = "/users"; // ❌

7. Satisfies pour la validation

L'opérateur satisfies (TypeScript 4.9+) valide sans élargir le type :

const config = {
  api: "https://api.example.com",
  timeout: 5000,
  retries: 3,
} satisfies Record<string, string | number>;

// config.api est toujours de type string (pas string | number)
config.api.toUpperCase(); // ✅ Fonctionne

Conclusion

Ces 7 patterns couvrent 90% de mes besoins quotidiens. Le reste, c'est de la lecture de documentation quand un cas spécifique se présente.

TypeScript m'a fait gagner un temps fou en debugging et en refactoring. Quand je renomme une propriété, le compilateur me montre instantanément tous les endroits à modifier. Quand un collègue (ou moi dans 6 mois) reprend mon code, les types servent de documentation vivante.

Mon conseil : activez le mode strict dès le jour 1. J'ai fait l'erreur de l'activer sur un projet existant de 50k lignes. Spoiler : 847 erreurs. Ne faites pas comme moi.

A

Amor GABTNI

Développeur Full Stack & Mobile

Articles similaires