Conventions TypeScript
TypeScript (TS) est très fortement recommandé, et avec la version stricte : TS ne doit pas émettre de fichier JavaScript si le compilateur sort en erreur.
Voir aussi
- Lint et formattage — Configuration ESLint et Prettier pour TypeScript
- Nommage — Conventions de nommage des variables et fichiers
any doit être évité au maximum et devrait être très rare.
object doit être proscrit, même Record<string | symbol, any> est préférable.
enum est proscrit, de même que namespace. Ces deux fonctionnalités ne respectent pas l’esprit de TS, qui doit être uniquement du typage :
Avec les enum, le compilateur rajoute du code JS qui n’existe pas dans le code TS initial. La philosophie de TS est que si on enlève simplement le typage, le code doit être du JavaScript valide. Ce n’est pas le cas avec les enum. Or on peut obtenir la même chose avec les unions.
Ex. :
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';au lieu de
enum HttpMethod {
Get = 'GET',
Post = 'POST',
Put = 'PUT',
Patch = 'PATCH',
}Au lieu d’utiliser les namespaces, il faut utiliser les modules. Cf. cette réponse sur StackOverflow, par exemple.
interface vs type
Les deux sont valables. Cependant, voici quelques recommandations :
- Utiliser
interfacepour décrire la forme d'un objet (contrats d'API, props de composants, entités). - Utiliser
typepour les unions, intersections, types utilitaires et types mappés.
// ✅ interface pour un objet / contrat
interface User {
id: number
name: string
email: string
}
// ✅ type pour une union
type Status = 'active' | 'inactive' | 'pending'
// ✅ type pour un type utilitaire
type PartialUser = Partial<User>
type UserWithoutId = Omit<User, 'id'>as const
Utiliser as const pour créer des constantes immuables avec des types littéraux inférés :
// ✅ Bien
const ROLES = ['admin', 'editor', 'viewer'] as const
type Role = typeof ROLES[number] // 'admin' | 'editor' | 'viewer'
const HTTP_STATUS = {
OK: 200,
NOT_FOUND: 404,
INTERNAL: 500,
} as constUnions discriminées
Les unions discriminées (ou tagged unions) sont un pattern puissant pour modéliser des états mutuellement exclusifs :
type ApiResponse<T> =
| { status: 'success'; data: T }
| { status: 'error'; error: string }
function handleResponse(response: ApiResponse<User>) {
if (response.status === 'success') {
// TypeScript sait que response.data existe ici
console.log(response.data.name)
} else {
// TypeScript sait que response.error existe ici
console.error(response.error)
}
}Validation runtime avec Zod
TypeScript ne valide les types qu'à la compilation. Pour valider les données à l'exécution (entrées utilisateur, réponses d'API, variables d'environnement), utiliser Zod :
import { z } from 'zod'
const UserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().positive(),
})
// Inférer le type TypeScript depuis le schéma Zod
type User = z.infer<typeof UserSchema>
// Validation à l'exécution
const result = UserSchema.safeParse(data)
if (result.success) {
// result.data est typé comme User
console.log(result.data.name)
} else {
console.error(result.error.issues)
}Types utilitaires
TypeScript propose des types utilitaires (utility types) à utiliser plutôt que de redéfinir manuellement les types :
// Partial<T> : toutes les propriétés deviennent optionnelles
type UpdateUser = Partial<User>
// Required<T> : toutes les propriétés deviennent obligatoires
type CompleteUser = Required<User>
// Pick<T, K> : sélectionner certaines propriétés
type UserPreview = Pick<User, 'id' | 'name'>
// Omit<T, K> : exclure certaines propriétés
type CreateUser = Omit<User, 'id' | 'createdAt'>
// Record<K, V> : créer un type d'objet avec clés/valeurs typées
type RolePermissions = Record<Role, string[]>
// ReturnType<T> : extraire le type de retour d'une fonction
type ServiceResult = ReturnType<typeof myService.getData>