TypeScript : Les pratiques qui changent tout

TypeScript Best Practices Architecture

TypeScript n’est pas juste “JavaScript avec des types”. C’est un outil de design puissant qui change la façon dont nous pensons notre code.

Strict Mode : Non négociable

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noPropertyAccessFromIndexSignature": true
  }
}

Le mode strict n’est pas une option, c’est la baseline. Il attrape 80% des bugs avant même l’exécution.

Types Utilitaires : Vos meilleurs amis

TypeScript offre des utilitaires puissants. Utilisez-les :

// Au lieu de dupliquer les interfaces
type UserFormData = Pick<User, 'name' | 'email'>;
type UserDTO = Omit<User, 'password'>;
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;

Discriminated Unions pour la logique complexe

Les unions discriminées éliminent les bugs et rendent le code auto-documenté :

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

function handleResult<T>(result: Result<T>) {
  if (result.success) {
    // TypeScript sait que result.data existe
    console.log(result.data);
  } else {
    // TypeScript sait que result.error existe
    console.error(result.error);
  }
}

Branded Types pour plus de sécurité

Évitez les confusions entre types similaires :

type UserId = string & { __brand: 'UserId' };
type Email = string & { __brand: 'Email' };

function getUser(id: UserId) { /* ... */ }

const email: Email = 'user@example.com' as Email;
// Erreur de compilation : Email n'est pas assignable à UserId
// getUser(email);

Template Literal Types

Typage fort des strings dynamiques :

type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type APIEndpoint = `/api/${string}`;

function request(method: HTTPMethod, endpoint: APIEndpoint) {
  // TypeScript garantit le format
}

request('GET', '/api/users'); // ✅
request('GET', 'users'); // ❌ Erreur de compilation

Inférence de types : Laissez TypeScript travailler

N’annotez pas tout. L’inférence est puissante :

// ❌ Redondant
const users: User[] = getUsers();

// ✅ L'inférence suffit si getUsers() retourne User[]
const users = getUsers();

Const Assertions pour l’immutabilité

const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
} as const;

// Type : { readonly apiUrl: "https://api.example.com"; readonly timeout: 5000 }
// Plus précis que { apiUrl: string; timeout: number }

Génériques : Ne pas en abuser

Les génériques ajoutent de la complexité. Utilisez-les quand ils apportent vraiment de la valeur :

// ✅ Utile
function identity<T>(value: T): T {
  return value;
}

// ❌ Inutile
function logMessage<T extends string>(msg: T): void {
  console.log(msg);
}
// Suffit :
function logMessage(msg: string): void {
  console.log(msg);
}

Conclusion

TypeScript bien utilisé n’est pas une contrainte mais un multiplicateur de productivité. Ces patterns éliminent des classes entières de bugs et rendent le code self-documenting.

Le temps investi à bien typer se rentabilise dès la première refactorisation.

← Retour au blog