TypeScript pour les développeurs JavaScript : le guide pratique
TypeScript n'est pas JavaScript avec des contraintes en plus. C'est JavaScript avec un filet de sécurité — une fois que vous voyez les erreurs que le compilateur attrape avant vous, vous ne revenez pas.
J'ai résisté à TypeScript pendant deux ans. "C'est du JavaScript avec des types — ça ralentit le développement pour peu de bénéfice." Puis j'ai rejoint un projet TypeScript et j'ai réalisé que le compilateur m'avait trouvé un bug dans les 20 premières minutes. Un bug que les tests n'auraient pas attrapé, et que je n'aurais découvert qu'en production.
TypeScript est un sur-ensemble de JavaScript : tout code JavaScript valide est du TypeScript valide. Vous ne repartez pas de zéro — vous ajoutez une couche de rigueur par-dessus ce que vous connaissez déjà.
Installation
npm install -D typescript ts-node @types/node
# Initialiser la config
npx tsc --init
# Ou pour un projet Node.js
npx tsc --init --target ES2022 --module NodeNext --strict true{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true, // Active toutes les vérifications strictes
"skipLibCheck": true,
"esModuleInterop": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}strict: true active strictNullChecks, noImplicitAny, strictFunctionTypes et d'autres. C'est ça qui rend TypeScript utile — sans strict, vous passez à côté de la moitié des erreurs.
Types de base
// Primitives
const name: string = 'William'
const age: number = 28
const isActive: boolean = true
// TypeScript infère les types — pas besoin de les écrire partout
const name = 'William' // inféré comme string
const age = 28 // inféré comme number
// Arrays
const tags: string[] = ['ts', 'react']
const scores: Array<number> = [1, 2, 3]
// Tuple — tableau avec types et longueur fixes
const point: [number, number] = [10, 20]
const entry: [string, number] = ['clé', 42]
// Union — plusieurs types possibles
let id: string | number
id = '42' // OK
id = 42 // OK
id = true // Erreur
// Literal types
type Direction = 'north' | 'south' | 'east' | 'west'
let direction: Direction = 'north' // OK
direction = 'up' // Erreur
// any — à éviter sauf cas exceptionnels
let anything: any = 'hello'
anything = 42 // OK — désactive les vérifications
// unknown — mieux que any quand le type est inconnu
let data: unknown = fetchData()
if (typeof data === 'string') {
data.toUpperCase() // OK — TypeScript sait que c'est un string ici
}Interfaces et types
// Interface — décrit la forme d'un objet
interface User {
id: number
name: string
email: string
role?: 'admin' | 'user' // ← ? = optionnel
readonly createdAt: Date // ← readonly = ne peut pas être modifié
}
// Type alias — plus flexible, utilisé pour les unions, intersections, etc.
type ID = string | number
type Nullable<T> = T | null
type UserOrNull = Nullable<User>
// Intersection — combiner deux types
type AdminUser = User & {
permissions: string[]
lastLogin: Date
}
// Interface vs type alias ?
// Convention : interfaces pour les objets/classes, types pour les unions et aliases
// En pratique, les deux sont interchangeables pour les objets// Étendre une interface
interface Employee extends User {
department: string
salary: number
}
// Merging de déclarations — uniquement avec interface
interface User {
id: number
name: string
}
interface User {
email: string // Fusionné avec le premier bloc
}
// Résultat : User a id, name et emailFonctions typées
// Typage des paramètres et du retour
function add(a: number, b: number): number {
return a + b
}
// Paramètres optionnels et valeurs par défaut
function greet(name: string, greeting: string = 'Bonjour'): string {
return `${greeting}, ${name}`
}
// Rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((acc, n) => acc + n, 0)
}
// Overloads — signatures multiples
function format(value: string): string
function format(value: number): string
function format(value: string | number): string {
return String(value)
}
// Type de fonction
type Handler = (event: MouseEvent) => void
type AsyncFn<T> = () => Promise<T>Generics : le vrai pouvoir de TypeScript
Les generics permettent d'écrire du code réutilisable qui préserve les informations de type.
// Sans generic — perd le type
function first(arr: any[]): any {
return arr[0]
}
const name = first(['Alice', 'Bob']) // name: any — information perdue
// Avec generic
function first<T>(arr: T[]): T {
return arr[0]
}
const name = first(['Alice', 'Bob']) // name: string ← type préservé
const num = first([1, 2, 3]) // num: number
// Contraindre le generic
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = { name: 'William', age: 28 }
getProperty(user, 'name') // string
getProperty(user, 'age') // number
getProperty(user, 'foo') // Erreur — 'foo' n'existe pas sur user
// Generic avec interface
interface ApiResponse<T> {
data: T
status: number
message: string
}
async function fetchUser(id: number): Promise<ApiResponse<User>> {
const res = await fetch(`/api/users/${id}`)
return res.json()
}
const response = await fetchUser(42)
response.data.name // string — TypeScript sait que data est un UserUtility Types : la boîte à outils TypeScript
TypeScript fourni des types utilitaires prêts à l'emploi.
interface User {
id: number
name: string
email: string
password: string
role: 'admin' | 'user'
}
// Partial — tous les champs optionnels
type UserUpdate = Partial<User>
// { id?: number; name?: string; email?: string; ... }
// Required — tous les champs obligatoires
type UserComplete = Required<UserUpdate>
// Pick — sélectionner certains champs
type UserPublic = Pick<User, 'id' | 'name'>
// { id: number; name: string }
// Omit — exclure certains champs
type UserSafe = Omit<User, 'password'>
// User sans le champ password
// Record — objet avec clés/valeurs typées
type Translations = Record<string, string>
const fr: Translations = { hello: 'Bonjour', bye: 'Au revoir' }
// Readonly — rendre tous les champs non-modifiables
type ImmutableUser = Readonly<User>
// ReturnType — extraire le type de retour d'une fonction
function createUser() { return { id: 1, name: 'Alice' } }
type CreatedUser = ReturnType<typeof createUser> // { id: number; name: string }
// Parameters — extraire les paramètres d'une fonction
function login(email: string, password: string) { ... }
type LoginParams = Parameters<typeof login> // [string, string]TypeScript avec React
interface UserCardProps {
user: {
id: number
name: string
avatar?: string
}
onEdit: (id: number) => void
className?: string
}
export function UserCard({ user, onEdit, className }: UserCardProps) {
return (
<div className={className}>
<img src={user.avatar ?? '/default-avatar.png'} alt={user.name} />
<h2>{user.name}</h2>
<button onClick={() => onEdit(user.id)}>Modifier</button>
</div>
)
}
// useState avec types
const [user, setUser] = useState<User | null>(null)
const [users, setUsers] = useState<User[]>([])
const [status, setStatus] = useState<'idle' | 'loading' | 'error'>('idle')
// useRef
const inputRef = useRef<HTMLInputElement>(null)
// Événements typés
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
}TypeScript s'intègre naturellement dans React et Vue — les deux frameworks ont un support TypeScript de première classe, et Next.js et Nuxt sont configurés TypeScript par défaut.
Migration JavaScript → TypeScript
La bonne approche est graduelle — pas besoin de réécrire tout en une fois.
{
"compilerOptions": {
"allowJs": true, // Accepter les fichiers .js
"checkJs": false, // Ne pas vérifier les .js (pour l'instant)
"strict": false, // Commencer sans strict
"noImplicitAny": false // Autoriser any implicite pendant la migration
}
}# Renommer un fichier JS en TS
mv src/utils.js src/utils.ts
# Ajouter @types pour les libs existantes
npm install -D @types/node @types/express @types/lodashStratégie en 4 étapes :
- Activer
allowJs: true— TypeScript coexiste avec JavaScript - Renommer les fichiers un par un en
.ts - Corriger les erreurs de type au fil de l'eau
- Activer
strict: truequand 80% du code est migré
TypeScript attrape en compilation ce que vous trouveriez sinon en production. Les erreurs JavaScript classiques — accès à une propriété d'undefined, mauvais type de paramètre, typo dans un nom de champ — deviennent des erreurs de compilation immédiates. Le coût d'apprentissage est réel mais limité dans le temps. Le bénéfice est permanent.