TypeScript
JavaScript avec typage statique — types primitifs, interfaces, génériques, types utilitaires, narrowing, tsconfig, React et Node. De la syntaxe de base aux patterns avancés.
Pourquoi TypeScript ?
function calculerTVA(prix, taux) {
return prix * taux;
}
calculerTVA("100", 0.2); // "1001001001..."
calculerTVA(100); // NaN
// Bugs silencieux au runtime !
function calculerTVA(
prix: number,
taux: number
): number {
return prix * taux;
}
calculerTVA("100", 0.2);
// ⛔ Erreur à la compilation !
TypeScript est un superset de JavaScript — tout JS valide est du TS valide. Le compilateur tsc transpile TS → JS. Les types n'existent qu'à la compilation : ils disparaissent au runtime.
# Installation
npm install -D typescript
npm install -D ts-node # exécuter sans compiler
npm install -D @types/node
# Initialiser tsconfig.json
npx tsc --init
# Compiler
npx tsc # compile tout le projet
npx tsc --watch # recompile à chaque sauvegarde
npx ts-node app.ts # exécuter directement
// hello.ts
const message: string = 'Bonjour TypeScript';
console.log(message);
// Avantages TypeScript :
// ✓ Erreurs détectées à la compilation, pas au runtime
// ✓ Autocomplétion dans l'IDE (IntelliSense)
// ✓ Refactoring sûr (renommer, déplacer)
// ✓ Documentation vivante via les types
// ✓ Interopérabilité avec les libs JS (@types)
Types primitifs & annotations
// Primitifs
let nom: string = 'Alice';
let age: number = 25;
let actif: boolean = true;
let inconnu: null = null;
let rien: undefined;
// Tableaux
const nombres: number[] = [1, 2, 3];
const noms: string[] = ['Alice', 'Bob'];
const mixed: Array<number | string> = [1, 'a'];
// Tuple — tableau de taille et types fixes
const point: [number, number] = [10, 20];
const entrée: [string, number] = ['age', 25];
const [clé, val] = entrée; // clé: string, val: number
// Types spéciaux
let a: any; // désactive le typage ← à éviter
let b: unknown; // sûr : forcer un check avant usage
let c: never; // valeur impossible (ex: switch exhaustif)
function loguer(msg: string): void { // pas de return
console.log(msg);
}
// Union — plusieurs types possibles
let id: string | number;
id = 'abc'; // ✓
id = 42; // ✓
id = true; // ⛔ Type 'boolean' non assignable
function afficherID(id: string | number) {
if (typeof id === 'string') {
console.log(id.toUpperCase()); // id est string ici
} else {
console.log(id.toFixed(2)); // id est number ici
}
}
// Literal types — valeurs exactes
type Direction = 'nord' | 'sud' | 'est' | 'ouest';
type Dé = 1 | 2 | 3 | 4 | 5 | 6;
type Toggle = true | false; // = boolean
function aller(dir: Direction) { ... }
aller('nord'); // ✓
aller('gauche'); // ⛔ Erreur !
// as const — inférer les literal types
const DIRECTIONS = ['nord', 'sud'] as const;
// type: readonly ["nord", "sud"]
type Dir = typeof DIRECTIONS[number]; // "nord" | "sud"
Interfaces & type aliases
interface User {
readonly id: number; // lecture seule
nom: string;
email: string;
age?: number; // optionnel
adresse?: Adresse;
saluer(): string; // méthode
}
interface Adresse {
rue: string;
ville: string;
cp?: string;
}
// Extension d'interface
interface Admin extends User {
role: 'admin' | 'superadmin';
permissions: string[];
}
// Fusion de déclarations (declaration merging)
interface Window {
monPlugin: { init(): void }; // ajouter à Window global
}
// Interface avec index signature
interface Dictionnaire<T> {
[clé: string]: T;
}
const scores: Dictionnaire<number> = {
Alice: 95, Bob: 87
};
// type alias — peut définir n'importe quel type
type ID = string | number;
type Callback = (err: Error | null, data: any) => void;
type Point = { x: number; y: number };
// Intersection — combiner des types
type UserAdmin = User & { role: string };
type WithTimestamp<T> = T & {
createdAt: Date;
updatedAt: Date;
};
// interface vs type — quand utiliser quoi ?
// interface :
// ✓ Objets et classes (forme attendue)
// ✓ Déclaration merging nécessaire
// ✓ Héritage (extends)
//
// type :
// ✓ Unions, intersections, tuples
// ✓ Types complexes (mapped, conditional)
// ✓ Alias d'autres types
//
// Pour les objets simples : interface ou type,
// les deux fonctionnent — choisir et rester cohérent
Fonctions typées
// Paramètres et retour typés
function add(a: number, b: number): number {
return a + b;
}
// Paramètre optionnel et valeur par défaut
function saluer(nom: string, titre?: string): string {
return titre ? `${titre} ${nom}` : nom;
}
// Rest parameter
function somme(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
// Type d'une fonction (signature)
type Comparateur<T> = (a: T, b: T) => number;
type Handler = (event: MouseEvent) => void;
// Function overloads — plusieurs signatures
function parse(val: string): number;
function parse(val: number): string;
function parse(val: string | number) {
return typeof val === 'string'
? Number(val) : String(val);
}
parse('42'); // → number
parse(42); // → string
// Non-null assertion ! — affirmer que ≠ null
const canvas = document
.querySelector<HTMLCanvasElement>('#canvas')!;
// Le ! dit : "je garantis que ce n'est pas null"
// Type assertion as
const input = document.getElementById('username')
as HTMLInputElement;
input.value; // OK — HTMLElement n'a pas .value
// satisfies (TS 4.9) — vérifier sans changer le type
const config = {
port: 3000,
host: 'localhost',
} satisfies { port: number; host: string };
config.port // inféré comme number (pas number|string)
config.port.toFixed(); // OK ✓
// Narrowing avec type predicates
function estString(val: unknown): val is string {
return typeof val === 'string';
}
function traiter(val: unknown) {
if (estString(val)) {
val.toUpperCase(); // val: string ici ✓
}
}
Classes & modificateurs
class BankAccount {
// Raccourci : déclarer et initialiser dans constructor
constructor(
public readonly owner: string, // public + readonly
private balance: number = 0, // privé TS
protected bank: string = 'BNP', // accessible sous-classes
) {}
get solde() { return this.balance; }
déposer(montant: number): this { // retourne this (fluent)
if (montant <= 0) throw new Error('Montant invalide');
this.balance += montant;
return this;
}
retirer(montant: number): this {
if (montant > this.balance) throw new Error('Solde insuffisant');
this.balance -= montant;
return this;
}
}
const compte = new BankAccount('Alice', 100);
compte.déposer(50).retirer(30); // fluent API
compte.balance; // ⛔ private → erreur TS
// Interface — contrat que la classe doit respecter
interface Repo<T, ID> {
findById(id: ID): Promise<T | null>;
findAll(): Promise<T[]>;
save(entity: T): Promise<T>;
delete(id: ID): Promise<void>;
}
class UserRepo implements Repo<User, number> {
async findById(id: number) { ... }
async findAll() { ... }
async save(user: User) { ... }
async delete(id: number) { ... }
}
// Classe abstraite — ne peut pas être instanciée
abstract class Shape {
abstract area(): number; // doit être implémenté
abstract perimeter(): number;
describe(): string { // méthode concrète
return `Aire=${this.area().toFixed(2)}`;
}
}
class Circle extends Shape {
constructor(private r: number) { super(); }
area() { return Math.PI * this.r ** 2; }
perimeter() { return 2 * Math.PI * this.r; }
}
Génériques (Generics)
Les génériques permettent d'écrire du code réutilisable et typé — une fonction ou classe qui fonctionne avec n'importe quel type, tout en conservant la sécurité du typage.
// Sans générique — perd le type
function identité(x: any): any { return x; }
const r = identité(42); // type: any ← on perd number
// Avec générique <T> — conserve le type
function identité<T>(x: T): T { return x; }
const r = identité(42); // type: number ✓
const s = identité('bonjour'); // type: string ✓
// Fonctions utilitaires génériques
function premier<T>(arr: T[]): T | undefined {
return arr[0];
}
function zip<A, B>(a: A[], b: B[]): [A, B][] {
return a.map((el, i) => [el, b[i]]);
}
zip([1,2,3], ['a','b','c']);
// [[1,'a'], [2,'b'], [3,'c']]
// Classe générique
class Stack<T> {
private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
peek(): T | undefined { return this.items.at(-1); }
get size(): number { return this.items.length; }
}
const stack = new Stack<number>();
stack.push(1); stack.push(2);
stack.pop(); // number ✓
// Contraindre T à avoir certaines propriétés
function getLength<T extends { length: number }>(x: T): number {
return x.length;
}
getLength('bonjour'); // ✓ string a .length
getLength([1,2,3]); // ✓ array a .length
getLength(42); // ⛔ number n'a pas .length
// keyof — contraindre à une clé existante
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { nom: 'Alice', age: 25 };
getProperty(user, 'nom'); // string ✓
getProperty(user, 'age'); // number ✓
getProperty(user, 'xyz'); // ⛔ 'xyz' n'est pas une clé de user
// Valeur par défaut de générique (TS 2.3+)
interface Response<T = unknown> {
data: T;
status: number;
message: string;
}
type UserResponse = Response<User>;
type AnyResponse = Response; // T = unknown par défaut
Types utilitaires
interface User {
id: number;
nom: string;
email: string;
motDePasse: string;
}
// Partial<T> — toutes les propriétés optionnelles
type UserUpdate = Partial<User>;
// { id?: number, nom?: string, ... }
// Required<T> — toutes obligatoires
type UserComplete = Required<User>;
// Readonly<T> — toutes en lecture seule
type UserFrozen = Readonly<User>;
// Pick<T, K> — garder seulement K
type UserPublic = Pick<User, 'id' | 'nom' | 'email'>;
// Omit<T, K> — enlever K
type UserSansMotDePasse = Omit<User, 'motDePasse'>;
// Record<K, V> — objet avec clés K et valeurs V
type Scores = Record<string, number>;
type PermissionsMap = Record<
'read' | 'write' | 'delete',
boolean
>;
// Exclude<T, U> — supprimer U de l'union T
type Nombres = string | number | boolean;
type SansString = Exclude<Nombres, string>;
// number | boolean
// Extract<T, U> — garder ce qui est dans les deux
type OnlyNumber = Extract<Nombres, number>;
// number
// NonNullable<T> — enlever null et undefined
type StringNonNulle = NonNullable<string | null | undefined>;
// string
// ReturnType<T> — type de retour d'une fonction
function getUser() { return { nom: 'Alice', age: 25 }; }
type UserResult = ReturnType<typeof getUser>;
// { nom: string; age: number }
// Parameters<T> — types des paramètres
type Params = Parameters<typeof getUser>;
// []
// Awaited<T> — type résolu d'une Promise (TS 4.5)
type Data = Awaited<Promise<User[]>>;
// User[]
// InstanceType<T> — type d'instance d'une classe
type UserInstance = InstanceType<typeof User>;
Types avancés
// Mapped type — itérer sur les clés d'un type
type Optional<T> = {
[K in keyof T]?: T[K]; // = Partial<T>
};
type ReadOnly<T> = {
readonly [K in keyof T]: T[K];
};
// Remapper les clés avec as
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
// User → { getNom: () => string, getAge: () => number }
type EventMap<T> = {
[K in keyof T as `on${Capitalize<string & K>}Changed`]:
(val: T[K]) => void;
};
// Template literal types
type EventName = `on${string}`;
type CSSProperty = `--${string}`;
type Route = `/${string}`;
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = `${Method} /${string}`;
// "GET /users", "POST /auth", etc.
// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
IsString<string>; // true
IsString<number>; // false
// infer — extraire un type conditionnel
type UnpackArray<T> = T extends (infer U)[] ? U : T;
UnpackArray<number[]>; // number
UnpackArray<string>; // string
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
UnpackPromise<Promise<User>>; // User
// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;
ToArray<string | number>;
// string[] | number[] (distribué sur l'union)
// DeepReadonly — récursif
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
Narrowing & type guards
type Animal = { nom: string; son: string };
type Chien = Animal & { race: string };
type Chat = Animal & { vies: number };
// typeof narrowing
function doublé(x: string | number) {
if (typeof x === 'string') return x.repeat(2);
return x * 2;
}
// instanceof narrowing
function traiterErreur(e: unknown) {
if (e instanceof ValidationError) { ... }
else if (e instanceof NetworkError) { ... }
else throw e;
}
// in narrowing — vérifier une propriété
function parler(a: Chien | Chat) {
if ('race' in a) {
console.log(`Chien race ${a.race}`); // a: Chien
} else {
console.log(`Chat avec ${a.vies} vies`); // a: Chat
}
}
// Discriminated union — champ discriminant commun
type Loading = { status: 'loading' };
type Success = { status: 'success'; data: User[] };
type Error = { status: 'error'; message: string };
type State = Loading | Success | Error;
function render(state: State) {
switch (state.status) {
case 'loading':
return 'Chargement...';
case 'success':
return state.data; // state: Success ✓
case 'error':
return state.message; // state: Error ✓
default:
// Exhaustive check — jamais atteint
const _exhaustive: never = state;
return _exhaustive;
// Si on ajoute un type à State sans gérer
// ce case → erreur TS ici ✓
}
}
// User-defined type guard
function isUser(val: unknown): val is User {
return (
typeof val === 'object' && val !== null &&
'id' in val && 'nom' in val
);
}
function traiter(val: unknown) {
if (isUser(val)) val.nom; // val: User ✓
}
tsconfig.json & outillage
{
"compilerOptions": {
// Cible d'émission JavaScript
"target": "ES2022", // version JS générée
"module": "NodeNext", // système de modules
"lib": ["ES2022", "DOM"], // APIs dispo
// Chemins
"rootDir": "./src",
"outDir": "./dist",
// Rigueur du typage
"strict": true, // active TOUT
"noImplicitAny": true, // interdit any implicite
"strictNullChecks": true, // null ≠ string
"strictFunctionTypes": true,
"noUncheckedIndexedAccess": true, // arr[0]: T|undefined
// Qualité de code
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
// Décorateurs (Angular, NestJS)
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
// Source maps pour le debug
"sourceMap": true,
"declaration": true // génère .d.ts
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
// 1. Object is possibly 'null' or 'undefined'
const el = document.getElementById('app'); // HTMLElement | null
el.innerHTML = 'hello'; // ⛔
// Solutions :
if (el) el.innerHTML = 'hello'; // guard ✅
el?.innerHTML = 'hello'; // optional chaining ✅
el!.innerHTML = 'hello'; // assertion (si certain) ✅
// 2. Property does not exist on type
const user: User = getUser();
user.foo; // ⛔ 'foo' n'existe pas sur User
// → Ajouter la propriété à l'interface, ou :
(user as any).foo; // dernier recours
// 3. Argument of type X is not assignable to Y
function fn(x: number) {}
fn('42'); // ⛔
fn(Number('42')); // ✅
// 4. Type 'X' has no call signatures
const val = config[key]; // type: any → problème ?
// Typer config avec Record<> ou interface
// 5. No overload matches this call
// → Vérifier que les arguments correspondent à
// une des signatures de surcharge
TypeScript + React
Dans un projet React+TS (create-react-app --template typescript ou Vite), les composants, hooks et contextes sont tous typables. L'inférence couvre ~80% des cas — ne type que ce qui est ambigu.
interface CardProps {
title: string;
count?: number; // optionnel
onClick: () => void;
children: React.ReactNode;
}
const Card: React.FC<CardProps> = ({ title, count = 0, onClick, children }) => (
<div onClick={onClick}>
<h2>{title} ({count})</h2>
{children}
</div>
);
// Inféré automatiquement ✓
const [count, setCount] = useState(0);
// Explicite si type non trivial
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);
// useRef
const inputRef = useRef<HTMLInputElement>(null);
// useReducer
type Action =
| { type: 'increment' }
| { type: 'set'; payload: number };
function reducer(state: number, action: Action): number {
switch (action.type) {
case 'increment': return state + 1;
case 'set': return action.payload;
}
}
interface FetchState<T> {
data: T | null;
loading: boolean;
error: string | null;
}
function useFetch<T>(url: string): FetchState<T> {
const [state, setState] = useState<FetchState<T>>({ data: null, loading: true, error: null });
useEffect(() => {
fetch(url)
.then(r => r.json() as Promise<T>)
.then(data => setState({ data, loading: false, error: null }))
.catch(e => setState({ data: null, loading: false, error: e.message }));
}, [url]);
return state;
}
// Usage — T est inféré depuis l'appel
const { data, loading } = useFetch<User[]>('/api/users');
TypeScript + Node.js
Pour un projet Node+TS, installer typescript, ts-node (exécution directe) et les types @types/node. Utiliser tsx pour le dev avec rechargement à chaud.
# Installation
npm install -D typescript ts-node @types/node
npm install -D tsx # dev rapide
# tsconfig.json recommandé pour Node
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"strict": true
}
}
npm install express
npm install -D @types/express
import express, { Request, Response } from 'express';
interface User { id: number; name: string; }
const app = express();
app.get('/users/:id', (req: Request, res: Response) => {
const id = parseInt(req.params.id);
const user: User = { id, name: 'Alice' };
res.json(user);
});
app.listen(3000);
Cheat Sheet TypeScript
📌 Types primitifs
string | Chaîne de caractères |
number | Entier ou décimal |
boolean | true / false |
null / undefined | Valeurs nulles |
unknown | Type inconnu — à vérifier |
never | Valeur impossible |
any | Désactive le typage |
void | Pas de valeur retournée |
🔧 Interfaces & types
interface A {} | Contrat extensible |
type A = {} | Alias (unions, mappés) |
prop?: T | Propriété optionnelle |
readonly x: T | Propriété immuable |
A extends B | Héritage d'interface |
A & B | Intersection |
A | B | Union |
⚙️ Génériques
function f<T>(x: T) | Générique simple |
T extends U | Contrainte |
keyof T | Clés d'un type |
T[K] | Valeur d'une clé |
Array<T> | Tableau générique |
Promise<T> | Promesse typée |
🛠 Types utilitaires
Partial<T> | Toutes props optionnelles |
Required<T> | Toutes props requises |
Readonly<T> | Toutes props immuables |
Pick<T, K> | Sélectionner des props |
Omit<T, K> | Exclure des props |
Record<K, V> | Map clé→valeur |
ReturnType<F> | Type de retour d'une fn |