TypeScript offre un systeme de types puissant qui va bien au-dela des simples annotations. Decouvrons ensemble des patterns avances pour tirer le meilleur parti du langage.
Types conditionnels
Les types conditionnels permettent de creer des types dynamiques bases sur des conditions :
type ApiResponse<T> = T extends Array<infer U> ? { data: U[]; total: number } : { data: T };
// Usage
type UserListResponse = ApiResponse<User[]>;
// Result: { data: User[]; total: number }
type SingleUserResponse = ApiResponse<User>;
// Result: { data: User }
Types mappes avec modificateurs
Les types mappes permettent de transformer les proprietes d’un type existant :
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface User {
name: string;
age: number;
email: string;
}
type UserGetters = Getters<User>;
// Result:
// {
// getName: () => string;
// getAge: () => number;
// getEmail: () => string;
// }
Type Guards personnalises
Les type guards permettent d’affiner le type d’une variable :
interface Success<T> {
status: 'success';
data: T;
}
interface Error {
status: 'error';
message: string;
}
type Result<T> = Success<T> | Error;
// Type guard personnalise
function isSuccess<T>(result: Result<T>): result is Success<T> {
return result.status === 'success';
}
// Usage
async function fetchUser(id: string): Promise<Result<User>> {
try {
const user = await api.getUser(id);
return { status: 'success', data: user };
} catch (e) {
return { status: 'error', message: e.message };
}
}
const result = await fetchUser('123');
if (isSuccess(result)) {
// TypeScript sait que result.data existe ici
console.log(result.data.name);
} else {
// TypeScript sait que result.message existe ici
console.error(result.message);
}
Template Literal Types
Les types litteraux de template permettent de creer des types de chaines dynamiques :
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiEndpoint = '/users' | '/products' | '/orders';
type ApiRoute = `${HttpMethod} ${ApiEndpoint}`;
// Result: "GET /users" | "GET /products" | ... | "DELETE /orders"
// Fonction type-safe pour les routes API
function createRoute<M extends HttpMethod, E extends ApiEndpoint>(
method: M,
endpoint: E
): `${M} ${E}` {
return `${method} ${endpoint}`;
}
const route = createRoute('POST', '/users');
// Type: "POST /users"
Branded Types
Les types “marques” permettent de distinguer des types structurellement identiques :
type Brand<T, B> = T & { __brand: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
function getUser(id: UserId): Promise<User> {
// ...
}
function getOrder(id: OrderId): Promise<Order> {
// ...
}
// Helper pour creer des IDs types
const createUserId = (id: string): UserId => id as UserId;
const createOrderId = (id: string): OrderId => id as OrderId;
// Usage
const userId = createUserId('user-123');
const orderId = createOrderId('order-456');
getUser(userId); // OK
getUser(orderId); // Erreur de compilation !
Conclusion
Ces patterns TypeScript avances peuvent sembler complexes au premier abord, mais ils permettent de creer des APIs plus sures et plus expressives. L’investissement dans la comprehension du systeme de types de TypeScript est largement rentabilise par la reduction des bugs en production.
Conseil : Commencez par les type guards et les types conditionnels simples avant de vous lancer dans les patterns plus avances.