7 Techniques Avancees de TypeScript que Vous Devriez Connaitre en 2025
Salut HaWkers, TypeScript s'est consolide comme le choix standard pour les projets JavaScript serieux. En 2025, plus de 78% des nouveaux projets web utilisent TypeScript des le debut, selon des recherches recentes de la communaute.
Vous maitrisez les bases de TypeScript mais sentez que vous ne faites qu'effleurer la surface ? Cet article va au-dela des interfaces et types basiques, explorant des techniques qui separent les developpeurs intermediaires des experts en TypeScript.
1. Strict Mode : La Base du Code Securise
Le mode strict de TypeScript n'est pas une technique unique, mais un ensemble de flags qui rendent votre code drastiquement plus sur. En 2025, les projets sans strict: true sont consideres comme legacy.
// tsconfig.json - Configuration moderne
{
"compilerOptions": {
"strict": true, // Active toutes les flags strict
"noUncheckedIndexedAccess": true, // TS 5.0+
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true
}
}Ce que beaucoup ne realisent pas est que strict: true active plusieurs flags individuellement :
noImplicitAny: Interdit lesanyimplicitesstrictNullChecks:nulletundefinedsont des types distinctsstrictFunctionTypes: Verification contravariante des parametresstrictBindCallApply: Typage correct pour.bind(),.call(),.apply()strictPropertyInitialization: Les proprietes de classe doivent etre initialiseesnoImplicitThis:thisdoit avoir un type explicite dans les contextes ambigus
Mais le vrai pouvoir vient des flags additionnelles comme noUncheckedIndexedAccess, introduite dans les versions plus recentes.
// Sans noUncheckedIndexedAccess
const users: Record<string, User> = {};
const user = users['123']; // Type: User
user.name; // Erreur runtime: Cannot read property 'name' of undefined
// Avec noUncheckedIndexedAccess
const users: Record<string, User> = {};
const user = users['123']; // Type: User | undefined
// user.name; // Erreur de compilation
user?.name; // SurCette flag seule previent d'innombrables bugs en production lies aux acces a des objets qui peuvent ne pas exister.
2. Template Literal Types : Des Types Dynamiques Puissants
Les Template Literal Types, introduits dans TypeScript 4.1 et etendus dans les versions suivantes, permettent de creer des types bases sur des strings de maniere dynamique.
// Creation de types dynamiques pour les evenements
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`;
// Type: 'onClick' | 'onFocus' | 'onBlur'
type EventHandlers = {
[K in HandlerName]: (event: Event) => void;
};
// Utilisation pratique
const handlers: EventHandlers = {
onClick: (e) => console.log('Clique'),
onFocus: (e) => console.log('Focus'),
onBlur: (e) => console.log('Blur')
};C'est extremement puissant pour les APIs qui suivent des conventions de nommage. Voyez un exemple reel avec des routes d'API :
// Systeme de routes type-safe
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Resource = 'user' | 'product' | 'order';
type Route = `/${Resource}` | `/${Resource}/${string}`;
type RouteHandler<M extends HTTPMethod, R extends Route> = {
method: M;
route: R;
handler: (req: Request) => Promise<Response>;
};
// Utilisation avec autocomplete total
const userRoute: RouteHandler<'GET', '/user'> = {
method: 'GET',
route: '/user',
handler: async (req) => {
// Implementation
return new Response();
}
};
// Erreur de compilation: 'PATCH' n'existe pas dans HTTPMethod
// const invalidRoute: RouteHandler<'PATCH', '/user'> = { ... };
3. Conditional Types et Inference : Logique dans les Types
Les Conditional Types permettent de creer une logique complexe dans le systeme de types, similaire aux operateurs ternaires en JavaScript.
// Type qui extrait le type de retour d'une Promise
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>; // number
// Application pratique: fonction qui retourne Promise ou valeur directe
async function fetchData<T>(
useCache: boolean,
data: T
): Promise<UnwrapPromise<T>> {
if (useCache) {
return data as UnwrapPromise<T>;
}
// Simule un fetch asynchrone
return new Promise(resolve =>
setTimeout(() => resolve(data as UnwrapPromise<T>), 100)
) as any;
}Un pattern avance est de combiner les conditional types avec les mapped types :
// Transforme les proprietes optionnelles en obligatoires et vice-versa
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
type RequiredKeys<T, K extends keyof T> = T & {
[P in K]-?: T[P];
};
interface User {
id: string;
name?: string;
email?: string;
}
// Rend name et email obligatoires
type FullUser = RequiredKeys<User, 'name' | 'email'>;
// Type: { id: string; name: string; email: string; }Ce pattern est incroyablement utile pour la validation de formulaires et les DTOs ou certaines proprietes deviennent obligatoires dans des contextes specifiques.
4. Branded Types : Securite dans les Types Primitifs
Les Branded Types (ou Nominal Types) resolvent un probleme commun : comment differencier des strings avec des significations differentes ?
// Probleme: userId et productId sont tous deux des strings
function getUser(id: string) { /* ... */ }
function getProduct(id: string) { /* ... */ }
const userId = "user123";
const productId = "prod456";
getUser(productId); // Bug, mais TypeScript ne se plaint pasSolution avec Branded Types :
// Creation de types branded
type Brand<K, T> = K & { __brand: T };
type UserId = Brand<string, 'UserId'>;
type ProductId = Brand<string, 'ProductId'>;
// Fonctions de creation (smart constructors)
function createUserId(id: string): UserId {
// Validation si necessaire
return id as UserId;
}
function createProductId(id: string): ProductId {
return id as ProductId;
}
// Utilisation type-safe
function getUser(id: UserId) { /* ... */ }
function getProduct(id: ProductId) { /* ... */ }
const userId = createUserId("user123");
const productId = createProductId("prod456");
getUser(userId); // OK
// getUser(productId); // Erreur de compilation !C'est specialement precieux dans les systemes financiers ou vous travaillez avec differentes devises ou IDs de differents domaines.
5. Utility Types Avances : Au-dela de Partial et Pick
TypeScript offre des utility types puissants, mais la plupart des developpeurs n'utilisent que Partial, Pick et Omit. Allons plus loin :
// ReturnType: Extrait le type de retour d'une fonction
function createUser() {
return {
id: '123',
name: 'John',
createdAt: new Date()
};
}
type User = ReturnType<typeof createUser>;
// Type: { id: string; name: string; createdAt: Date; }
// Parameters: Extrait les types de parametres d'une fonction
function updateUser(id: string, data: { name: string; email: string }) {
// Implementation
}
type UpdateUserParams = Parameters<typeof updateUser>;
// Type: [id: string, data: { name: string; email: string; }]
// Awaited: Unwrap Promise types (TS 4.5+)
type Response = Awaited<Promise<{ data: string }>>;
// Type: { data: string; }
// Combinaison d'utility types
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
type UserKeys = keyof User; // 'id' | 'name' | 'createdAt'Un pattern puissant est de creer vos propres utility types customises :
// DeepPartial: Rend toutes les proprietes imbriquees optionnelles
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
interface Config {
database: {
host: string;
port: number;
credentials: {
user: string;
password: string;
};
};
cache: {
ttl: number;
};
}
// Permet des updates partiels profonds
function updateConfig(config: DeepPartial<Config>) {
// Vous pouvez passer seulement ce que vous voulez mettre a jour
}
updateConfig({
database: {
credentials: {
password: 'new-password' // Seulement password, le reste est optionnel
}
}
});
6. Discriminated Unions : Type Guards Automatiques
Les Discriminated Unions (ou Tagged Unions) sont un pattern qui rend les etats impossibles impossibles a representer.
// Etat de requete utilisant une union discriminee
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function handleRequest<T>(state: RequestState<T>) {
// TypeScript restreint le type automatiquement
switch (state.status) {
case 'idle':
// state.status est 'idle'
// state.data n'existe pas (erreur de compilation si vous essayez d'acceder)
return 'En attente...';
case 'loading':
return 'Chargement...';
case 'success':
// TypeScript sait que state.data existe ici !
return `Succes: ${state.data}`;
case 'error':
// TypeScript sait que state.error existe ici !
return `Erreur: ${state.error.message}`;
}
}
// Utilisation dans des composants React
function UserProfile() {
const [userState, setUserState] =
useState<RequestState<User>>({ status: 'idle' });
useEffect(() => {
setUserState({ status: 'loading' });
fetchUser()
.then(data => setUserState({ status: 'success', data }))
.catch(error => setUserState({ status: 'error', error }));
}, []);
return <div>{handleRequest(userState)}</div>;
}Ce pattern elimine les bugs communs ou vous accedez a data quand vous etes en etat de loading, ou essayez de montrer error quand en fait vous avez un succes.
7. Type Predicates : Type Guards Customises
Les type predicates permettent de creer des fonctions qui informent TypeScript sur le type d'une valeur au runtime.
// Type predicate basique
function isString(value: unknown): value is string {
return typeof value === 'string';
}
// Utilisation
function processValue(value: string | number) {
if (isString(value)) {
// TypeScript sait que value est string ici
console.log(value.toUpperCase());
} else {
// TypeScript sait que value est number ici
console.log(value.toFixed(2));
}
}
// Type predicate avance avec validation
interface User {
id: string;
name: string;
email: string;
}
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'name' in obj &&
'email' in obj &&
typeof (obj as any).id === 'string' &&
typeof (obj as any).name === 'string' &&
typeof (obj as any).email === 'string'
);
}
// Utilisation avec les reponses API
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
if (!isUser(data)) {
throw new Error('Invalid user data from API');
}
// TypeScript sait que data est User ici
return data;
}
Bonus : Integration avec Zod pour la Validation Runtime
TypeScript verifie les types au compile-time, mais les APIs retournent des donnees au runtime. Zod comble cette lacune :
import { z } from 'zod';
// Schema Zod
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(2).max(50),
email: z.string().email(),
age: z.number().int().positive().optional()
});
// Type TypeScript infere automatiquement
type User = z.infer<typeof UserSchema>;
// Validation au runtime avec type narrowing
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
// Parse valide et retourne User ou lance une erreur
return UserSchema.parse(data);
}
// Validation sure sans exceptions
const result = UserSchema.safeParse(unknownData);
if (result.success) {
// result.data est User
console.log(result.data.name);
} else {
// result.error contient les details
console.error(result.error.errors);
}Cette combinaison de TypeScript avec Zod est le standard de reference en 2025 pour les applications qui traitent des donnees externes.
L'Impact Reel de Ces Techniques
Implementer ces techniques n'est pas pour ecrire du code "plus joli" - c'est pour prevenir les bugs. Dans une etude de 2024, les equipes qui ont adopte strict mode + branded types + discriminated unions ont rapporte :
- 38% moins de bugs en production lies aux types incorrects
- 22% de reduction du temps de debugging
- Augmentation de 15% de la confiance lors du refactoring
TypeScript bien utilise transforme votre editeur en assistant qui previent les erreurs avant meme que vous n'executiez le code.
Vous voulez mieux comprendre les fondamentaux de JavaScript qui rendent TypeScript possible ? Consultez mon article sur la Programmation Fonctionnelle en JavaScript ou vous decouvrirez des concepts que TypeScript utilise pour l'inference de types.

