JavaScript · Typage statique

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 ?

🟡 JavaScript
function calculerTVA(prix, taux) {
  return prix * taux;
}

calculerTVA("100", 0.2); // "1001001001..."
calculerTVA(100);        // NaN
// Bugs silencieux au runtime !
🟣 TypeScript
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 & premier fichier
# 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

Types de base
// 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, intersection, literal types
// 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 — contrat de forme
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 — plus flexible
// 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

Signatures et overloads
// 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
Assertions et non-null
// 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

Modificateurs d'accès & propriétés
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
Implémentation d'interface & classes abstraites
// 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.

Fonctions et classes génériques
// 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 ✓
Contraintes — extends
// 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

Transformation d'objets
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
>;
Types conditionnels & autres utilitaires
// 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 types — transformer chaque propriété
// 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.
Conditional types — types qui dépendent d'autres
// 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

Techniques de narrowing
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 unions & exhaustive check
// 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

tsconfig.json — options essentielles
{
  "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"]
}
Erreurs TS communes & solutions
// 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.

Props typées
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>
);
useState typé
// 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 & useReducer typés
// 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;
  }
}
Hook custom typé — useFetch
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.

Setup
# 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
  }
}
Express typé
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

stringChaîne de caractères
numberEntier ou décimal
booleantrue / false
null / undefinedValeurs nulles
unknownType inconnu — à vérifier
neverValeur impossible
anyDésactive le typage
voidPas de valeur retournée

🔧 Interfaces & types

interface A {}Contrat extensible
type A = {}Alias (unions, mappés)
prop?: TPropriété optionnelle
readonly x: TPropriété immuable
A extends BHéritage d'interface
A & BIntersection
A | BUnion

⚙️ Génériques

function f<T>(x: T)Générique simple
T extends UContrainte
keyof TClé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