JavaScript Avancé

JS Avancé
& TypeScript

Prototype, classes, générateurs, modules, design patterns, performance — et TypeScript du typage de base aux génériques avancés.

Prototype & héritage prototypal

JavaScript n'a pas d'héritage classique — il utilise la chaîne de prototypes. Chaque objet a un lien vers un prototype dont il hérite des propriétés et méthodes.

Prototype — comment ça marche
// Tout objet a [[Prototype]] (accessible via __proto__)
const animal = {
  parler() { return `${this.nom} fait du bruit`; },
};

const chien = Object.create(animal);
chien.nom = 'Rex';
chien.aboyer = function() { return 'Wouf!'; };

chien.parler(); // 'Rex fait du bruit'
// parler() introuvable sur chien → cherche dans animal ✓

// Chaîne de prototypes :
// chien → animal → Object.prototype → null
Object.getPrototypeOf(chien) === animal; // true

// hasOwnProperty — distingue propre vs hérité
chien.hasOwnProperty('nom');    // true (propre)
chien.hasOwnProperty('parler'); // false (hérité)

// instanceof — vérifie la chaîne
function Animal(nom) { this.nom = nom; }
const a = new Animal('Rex');
a instanceof Animal; // true
Héritage via prototype (avant classes)
// Fonction constructeur
function Animal(nom, son) {
  this.nom = nom;
  this.son  = son;
}
Animal.prototype.parler = function() {
  return `${this.nom} fait "${this.son}"`;
};

// Sous-classe (héritage prototypal)
function Chien(nom) {
  Animal.call(this, nom, 'Wouf'); // appel parent
}
Chien.prototype = Object.create(Animal.prototype);
Chien.prototype.constructor = Chien;

Chien.prototype.chercher = function() {
  return `${this.nom} rapporte la balle !`;
};

const rex = new Chien('Rex');
rex.parler();   // 'Rex fait "Wouf"'
rex.chercher(); // 'Rex rapporte la balle !'

// Today → toujours préférer les classes ES6
// qui font la même chose de façon lisible

Classes ES6+

Syntaxe complète — toutes les fonctionnalités
class Animal {
  // Champ de classe (ES2022)
  #nom;                     // privé (# obligatoire)
  son = '...';             // public avec valeur par défaut
  static count = 0;        // propriété statique

  constructor(nom, son) {
    this.#nom = nom;
    this.son  = son;
    Animal.count++;
  }

  // Getter / Setter
  get nom()      { return this.#nom; }
  set nom(val)   {
    if (!val) throw new Error('Nom requis');
    this.#nom = val;
  }

  parler()  { return `${this.#nom}: ${this.son}`; }

  // Méthode statique — appelée sur la classe
  static compter() { return Animal.count; }

  // Méthode privée
  #valider() { return this.#nom.length > 0; }
}

class Chien extends Animal {
  constructor(nom) {
    super(nom, 'Wouf'); // OBLIGATOIRE avant this
  }

  // Override de méthode
  parler() {
    return `${super.parler()} 🐕`;
  }
}
Mixins — composition vs héritage
// JS ne supporte pas l'héritage multiple.
// Les mixins permettent de composer des comportements.

const Serializable = (Base) => class extends Base {
  toJSON() { return JSON.stringify(this); }
  static fromJSON(json) {
    return Object.assign(new this(), JSON.parse(json));
  }
};

const Timestamped = (Base) => class extends Base {
  constructor(...args) {
    super(...args);
    this.createdAt = new Date();
  }
};

const Loggable = (Base) => class extends Base {
  log() { console.log(JSON.stringify(this)); }
};

// Composer plusieurs comportements :
class User extends Loggable(Timestamped(Serializable(Animal))) {
  constructor(nom, email) {
    super(nom, '');
    this.email = email;
  }
}

const u = new User('Alice', 'alice@ex.com');
u.log();       // méthode de Loggable
u.toJSON();    // méthode de Serializable
u.createdAt;   // propriété de Timestamped

Itérateurs & générateurs

Protocole itérable & itérateur personnalisé
// Un itérable implémente [Symbol.iterator]()
// qui retourne un itérateur { next() }

class Intervalle {
  constructor(debut, fin, pas = 1) {
    this.debut = debut;
    this.fin   = fin;
    this.pas   = pas;
  }

  [Symbol.iterator]() {
    let courant = this.debut;
    const fin   = this.fin;
    const pas   = this.pas;

    return {
      next() {
        if (courant <= fin) {
          const value = courant;
          courant += pas;
          return { value, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
}

for (const n of new Intervalle(1, 10, 2)) {
  console.log(n); // 1, 3, 5, 7, 9
}
[...new Intervalle(1, 5)]  // [1,2,3,4,5]
Générateurs — function*
// Un générateur est une fonction pausable.
// yield suspend et retourne une valeur.

function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {        // séquence infinie !
    yield a;
    [a, b] = [b, a + b];
  }
}

const fib = fibonacci();
fib.next().value; // 0
fib.next().value; // 1
fib.next().value; // 1
fib.next().value; // 2

// Prendre les 10 premiers
function* take(n, iterable) {
  let count = 0;
  for (const x of iterable) {
    if (count++ >= n) return;
    yield x;
  }
}
[...take(10, fibonacci())]
// [0,1,1,2,3,5,8,13,21,34]

// yield* — déléguer à un autre itérable
function* concat(...arrays) {
  for (const arr of arrays) yield* arr;
}
[...concat([1,2], [3,4], [5])]
// [1,2,3,4,5]

Proxy & Reflect

Proxy — intercepter les opérations sur un objet
// Proxy intercepte : get, set, has, deleteProperty,
// apply (appels de fonction), construct, etc.

const handler = {
  // Intercepter la lecture d'une propriété
  get(target, prop, receiver) {
    if (prop in target) {
      console.log(`Lecture de ${prop}`);
      return Reflect.get(target, prop, receiver);
    }
    throw new ReferenceError(`Propriété "${prop}" inconnue`);
  },

  // Intercepter l'écriture
  set(target, prop, value) {
    if (prop === 'age' && typeof value !== 'number') {
      throw new TypeError('age doit être un nombre');
    }
    return Reflect.set(target, prop, value);
  },
};

const user = new Proxy({ nom: 'Alice', age: 25 }, handler);
user.nom;        // log + 'Alice'
user.age = 'x'; // TypeError !
user.email;      // ReferenceError !
Proxy — cas d'usage réels
// 1. Observable — détecter les changements
function observable(obj, onChange) {
  return new Proxy(obj, {
    set(target, prop, value) {
      const old = target[prop];
      const ok  = Reflect.set(target, prop, value);
      if (old !== value) onChange(prop, old, value);
      return ok;
    }
  });
}

const state = observable(
  { count: 0 },
  (prop, from, to) => console.log(`${prop}: ${from}→${to}`)
);
state.count++; // "count: 0→1"

// 2. Cache avec expiration
function withCache(target, ttl = 5000) {
  const cache = new Map();
  return new Proxy(target, {
    get(t, prop) {
      if (typeof t[prop] !== 'function') return t[prop];
      return async (...args) => {
        const key = `${prop}:${JSON.stringify(args)}`;
        const hit = cache.get(key);
        if (hit && Date.now() - hit.ts < ttl) return hit.val;
        const val = await t[prop](...args);
        cache.set(key, { val, ts: Date.now() });
        return val;
      };
    }
  });
}

Modules ES & CommonJS

ES Modules (ESM) — standard moderne
// math.js — exports nommés
export const PI = 3.14159;
export function somme(a, b) { return a + b; }
export class Vecteur { ... }

// api.js — export par défaut + nommés
export default class ApiClient { ... }
export { fetchUser, fetchPost };

// main.js — imports
import { PI, somme } from './math.js';
import { somme as add } from './math.js';  // alias
import * as Math from './math.js';          // namespace
import ApiClient from './api.js';           // défaut
import ApiClient, { fetchUser } from './api.js'; // les deux

// Import dynamique (lazy loading)
const module = await import('./lourd.js');
// Chargé seulement quand nécessaire !

// Import conditionnel
const { default: chart } = await import(
  isMobile ? './chart-mobile.js' : './chart-desktop.js'
);
CommonJS (Node.js) vs ESM
// ── CommonJS (require/module.exports) ──────
// math.js
const PI = 3.14;
function somme(a, b) { return a + b; }
module.exports = { PI, somme };
// ou :
module.exports.somme = somme;

// main.js
const { PI, somme } = require('./math');
const fs = require('fs');  // module Node natif

// ── Différences clés ──────────────────────
// ESM    : import statique → tree-shaking possible
// CJS    : require dynamique → pas de tree-shaking
// ESM    : top-level await supporté
// CJS    : synchrone uniquement
// ESM    : mode strict par défaut
// CJS    : mode non-strict par défaut

// package.json pour ESM dans Node :
// { "type": "module" }
// ou extension .mjs pour les fichiers ESM

// Interop : importer CJS depuis ESM
import pkg from 'legacy-cjs-package'; // import défaut
const { fn } = pkg;

Design patterns JavaScript

Création Singleton
// Instance unique — ex: connexion DB, config
class ConfigService {
  static #instance = null;
  #config = {};

  constructor() {
    if (ConfigService.#instance) {
      return ConfigService.#instance;
    }
    ConfigService.#instance = this;
  }

  set(key, val) { this.#config[key] = val; }
  get(key)      { return this.#config[key]; }
}

const c1 = new ConfigService();
const c2 = new ConfigService();
c1 === c2; // true — même instance
Comportement Observer / EventEmitter
class EventEmitter {
  #handlers = new Map();

  on(event, fn) {
    if (!this.#handlers.has(event))
      this.#handlers.set(event, new Set());
    this.#handlers.get(event).add(fn);
    return () => this.off(event, fn); // unsubscribe fn
  }

  off(event, fn) { this.#handlers.get(event)?.delete(fn); }

  emit(event, ...args) {
    this.#handlers.get(event)?.forEach(fn => fn(...args));
  }
}

const bus = new EventEmitter();
const off = bus.on('login', user => console.log(user));
bus.emit('login', { nom: 'Alice' });
off(); // se désabonner
Structure Decorator
// Ajouter des comportements sans modifier la classe
function withLogging(fn) {
  return function(...args) {
    console.log(`Appel: ${fn.name}(${args})`);
    const result = fn.apply(this, args);
    console.log(`Résultat: ${result}`);
    return result;
  };
}

const somme = (a, b) => a + b;
const sommeLoggée = withLogging(somme);
sommeLoggée(2, 3); // log + 5
Création Factory & Builder
// Factory — créer des objets sans new
function créerBouton(type) {
  const types = {
    primary: { bg: 'blue',  label: 'Confirmer' },
    danger:  { bg: 'red',   label: 'Supprimer' },
  };
  return { ...types[type], onClick: () => {} };
}

// Builder — construction par étapes
class QueryBuilder {
  #parts = { select: '*', where: [], limit: null };

  select(...cols) { this.#parts.select = cols; return this; }
  where(cond)     { this.#parts.where.push(cond); return this; }
  limit(n)        { this.#parts.limit = n; return this; }
  build() { return `SELECT ${this.#parts.select}...`; }
}

const query = new QueryBuilder()
  .select('nom', 'email')
  .where('actif = true')
  .limit(10)
  .build();

Performance & optimisation

WeakMap, WeakSet, WeakRef
// Map ordinaire — empêche le garbage collector
const cache = new Map();
cache.set(domElement, data); // domElement ne sera
// jamais libéré tant que cache existe !

// WeakMap — référence faible, GC peut libérer
const metadonnées = new WeakMap();
metadonnées.set(domElement, { clics: 0 });
// Si domElement est supprimé du DOM,
// il sera garbage-collecté automatiquement

// WeakMap — cache privé pour les classes
const _privé = new WeakMap();
class Sécurisé {
  constructor() { _privé.set(this, { secret: 42 }); }
  get val() { return _privé.get(this).secret; }
}

// WeakRef — référence faible à un objet
let ref = new WeakRef(grosObjet);
const obj = ref.deref();
if (obj) { /* objet toujours en vie */ }
Optimisations DOM & async
// DocumentFragment — batch d'insertions
const fragment = document.createDocumentFragment();
items.forEach(item => {
  const li = document.createElement('li');
  li.textContent = item;
  fragment.appendChild(li); // pas de reflow ici
});
ul.appendChild(fragment); // 1 seul reflow !

// Throttle — max 1 appel par intervalle
function throttle(fn, delay) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= delay) {
      last = now;
      fn(...args);
    }
  };
}
window.addEventListener('scroll', throttle(fn, 100));

// requestAnimationFrame — animations fluides
function animer(progress) {
  element.style.transform = `translateX(${progress}px)`;
  if (progress < 100)
    requestAnimationFrame(() => animer(progress + 1));
}
requestAnimationFrame(() => animer(0));

// Web Workers — calcul hors du thread UI
const worker = new Worker('worker.js');
worker.postMessage({ donnees: tableauLourd });
worker.onmessage = (e) => afficher(e.data.résultat);

Gestion d'erreurs avancée

Classes d'erreurs personnalisées
// Erreurs typées pour un meilleur catch
class AppError extends Error {
  constructor(message, code, context = {}) {
    super(message);
    this.name    = 'AppError';
    this.code    = code;
    this.context = context;
    Error.captureStackTrace(this, AppError); // Node
  }
}

class NotFoundError    extends AppError {
  constructor(ressource, id) {
    super(`${ressource} #${id} introuvable`, 'NOT_FOUND');
    this.name = 'NotFoundError';
  }
}

class ValidationError extends AppError {
  constructor(champ, règle) {
    super(`${champ} invalide : ${règle}`, 'VALIDATION');
    this.name = 'ValidationError';
  }
}

// Catch typé :
try {
  await getUser(42);
} catch (err) {
  if (err instanceof NotFoundError)   afficher404();
  else if (err instanceof ValidationError) afficherForm();
  else throw err; // re-propager les inconnues
}
Result pattern — éviter les try/catch
// Inspiré de Rust — retourner {ok, err} plutôt que throw
function ok(value) { return { ok: true, value }; }
function err(error) { return { ok: false, error }; }

async function safeGetUser(id) {
  try {
    const user = await fetchUser(id);
    return ok(user);
  } catch (e) {
    return err(e);
  }
}

// Usage — pas de try/catch à chaque appel
const résultat = await safeGetUser(42);
if (!résultat.ok) {
  afficherErreur(résultat.error);
  return;
}
afficher(résultat.value);

// Erreurs non gérées — toujours écouter
window.addEventListener('unhandledrejection', (e) => {
  console.error('Promise non gérée :', e.reason);
  // logger.error(e.reason);
});

process.on('unhandledRejection', (reason) => {
  // Node.js
  console.error(reason);
  process.exit(1);
});

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 TypeScript & 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 intégrés

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, conditional, template

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

Cheat sheet JS Avancé + TypeScript

JS Avancé — rappels

Prototype chainCherche propriété → parent → Object
Classe privée#champ (natif ES2022)
Mixin(Base) => class extends Base
function*Générateur — yield suspend
ProxyIntercepter get/set/apply
WeakMapRéférence faible → GC peut libérer

TypeScript — types clés

anyDésactive le typage — à éviter
unknownSûr — vérifier avant usage
neverValeur impossible — switch exhaustif
T | UUnion — l'un ou l'autre
T & UIntersection — les deux
keyof TUnion des clés de T

Génériques

fn<T>(x: T): TInférer et conserver le type
T extends UT doit avoir les props de U
keyof TLimiter aux clés existantes
infer UExtraire un type conditionnel
T = DefaultValeur par défaut du générique

Types utilitaires

Partial<T>Toutes optionnelles
Required<T>Toutes obligatoires
Pick<T, K>Garder K seulement
Omit<T, K>Enlever K
Record<K, V>Objet clés K → valeurs V
ReturnType<F>Type de retour de F