TypeScript : Types avancés et utilitaires

TypeScript : Types avancés et utilitaires

Installation et configuration

Installation

Installation globale

npm install -g typescript

Installation dans projet

npm install --save-dev typescript npm install --save-dev @types/node

Initialiser tsconfig.json

npx tsc --init

Compiler

npx tsc npx tsc --watch # Mode watch

Avec ts-node (exécution directe)

npm install --save-dev ts-node npx ts-node script.ts

tsconfig.json recommandé

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "@/": ["src/"],
      "@components/": ["src/components/"],
      "@utils/": ["src/utils/"]
    }
  },
  "include": ["src//"],
  "exclude": ["nodemodules", "dist"]
}

Types de base

Types primitifs

// Boolean
let isDone: boolean = false;
let isActive: boolean = true;

// Number
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;

// String
let color: string = "blue";
let sentence: string = La couleur est ${color};

// Array
let list: number[] = [1, 2, 3];
let list2: Array = [1, 2, 3];
let matrix: number[][] = [[1, 2], [3, 4]];

// Tuple
let tuple: [string, number] = ["hello", 10];
let rgb: [number, number, number] = [255, 0, 0];

// Enum
enum Color {
  Red,
  Green,
  Blue
}
let c: Color = Color.Green;

// Enum avec valeurs
enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT"
}

// Const enum (optimisé)
const enum Status {
  Active = 1,
  Inactive = 0
}

// Any (éviter si possible)
let notSure: any = 4;
notSure = "maybe a string";
notSure = false;

// Unknown (préférer à any)
let value: unknown = 4;
if (typeof value === "number") {
  let num: number = value;  // OK après type guard
}

// Void
function logMessage(message: string): void {
  console.log(message);
}

// Null et Undefined
let u: undefined = undefined;
let n: null = null;

// Never (ne retourne jamais)
function error(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}

// Object
let obj: object = { name: "John" };
let obj2: { name: string; age: number } = {
  name: "John",
  age: 30
};

Type assertions

// Deux syntaxes
let someValue: unknown = "this is a string";
let strLength1: number = (someValue as string).length;
let strLength2: number = (someValue).length;

// Non-null assertion (!)
function processValue(value: string | null) {
  // Assert que value n'est pas null
  console.log(value!.toUpperCase());
}

// Const assertion
let x = "hello" as const;  // Type: "hello", pas string
let arr = [1, 2, 3] as const;  // Type: readonly [1, 2, 3]

let obj = {
  name: "John",
  age: 30
} as const;  // Propriétés readonly

// Satisfies (TypeScript 4.9+)
type Color = { r: number; g: number; b: number } | string;

const red = { r: 255, g: 0, b: 0 } satisfies Color;
// red.r est accessible (type inféré)

const blue = "#0000FF" satisfies Color;
// blue.toUpperCase() est accessible

Interfaces et Types

Interfaces

// Interface basique
interface User {
  id: number;
  name: string;
  email: string;
}

const user: User = {
  id: 1,
  name: "John",
  email: "john@example.com"
};

// Propriétés optionnelles
interface Config {
  color: string;
  width?: number;  // Optionnel
  height?: number;
}

// Propriétés readonly
interface Point {
  readonly x: number;
  readonly y: number;
}

let p: Point = { x: 10, y: 20 };
// p.x = 5;  // Erreur

// Index signatures
interface StringArray {
  [index: number]: string;
}

let myArray: StringArray = ["Bob", "Fred"];

interface Dictionary {
  [key: string]: any;
}

let dict: Dictionary = {
  name: "John",
  age: 30
};

// Méthodes
interface Calculator {
  add(a: number, b: number): number;
  subtract(a: number, b: number): number;
}

// Ou avec syntaxe propriété
interface Calculator2 {
  add: (a: number, b: number) => number;
  subtract: (a: number, b: number) => number;
}

// Extension d'interface
interface Person {
  name: string;
  age: number;
}

interface Employee extends Person {
  employeeId: string;
  department: string;
}

// Extension multiple
interface Timestamped {
  createdAt: Date;
  updatedAt: Date;
}

interface Article extends Person, Timestamped {
  title: string;
  content: string;
}

// Fusion d'interfaces (declaration merging)
interface Window {
  title: string;
}

interface Window {
  theme: string;
}

// Window a maintenant title et theme

// Call signatures
interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc = function(src, sub) {
  return src.search(sub) > -1;
};

// Construct signatures
interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
  tick(): void;
}

Type aliases

// Type alias basique
type ID = string | number;
type Point = { x: number; y: number };

// Union types
type Status = "pending" | "active" | "inactive";
type Result = Success | Failure;

// Intersection types
type Person = { name: string; age: number };
type Employee = { employeeId: string; department: string };
type Worker = Person & Employee;

const worker: Worker = {
  name: "John",
  age: 30,
  employeeId: "E123",
  department: "IT"
};

// Tuple types
type RGB = [number, number, number];
type RGBA = [...RGB, number];

// Function types
type MathOperation = (a: number, b: number) => number;
type Predicate = (value: T) => boolean;

// Recursive types
type JSONValue =
  | string
  | number
  | boolean
  | null
  | JSONValue[]
  | { [key: string]: JSONValue };

type TreeNode = {
  value: T;
  children?: TreeNode[];
};

// Template literal types
type Greeting = Hello ${string};
type EventName = on${Capitalize};

// Interface vs Type
// Interface peut être étendue par declaration merging
// Type peut faire unions, intersections, mapped types
// Interface meilleure pour objets publics (classes, APIs)
// Type meilleur pour compositions complexes

Génériques

Bases des génériques

// Fonction générique
function identity(arg: T): T {
  return arg;
}

let output1 = identity("hello");
let output2 = identity(42);  // Type inféré

// Array générique
function firstElement(arr: T[]): T | undefined {
  return arr[0];
}

const first = firstElement([1, 2, 3]);  // number | undefined

// Multiple type parameters
function pair(first: T, second: U): [T, U] {
  return [first, second];
}

const p = pair("hello", 42);  // [string, number]

// Constraints (contraintes)
interface HasLength {
  length: number;
}

function logLength(arg: T): void {
  console.log(arg.length);
}

logLength("hello");  // OK
logLength([1, 2, 3]);  // OK
logLength({ length: 10, value: 3 });  // OK
// logLength(42);  // Erreur

// Contraintes avec keyof
function getProperty(obj: T, key: K): T[K] {
  return obj[key];
}

const person = { name: "John", age: 30 };
const name = getProperty(person, "name");  // string
const age = getProperty(person, "age");    // number
// getProperty(person, "invalid");  // Erreur

// Default type parameters
function createArray(length: number, value: T): T[] {
  return Array(length).fill(value);
}

const arr1 = createArray(3, "x");  // string[]
const arr2 = createArray(3, 0);  // number[]

Génériques avec classes

// Classe générique
class Container {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }

  setValue(value: T): void {
    this.value = value;
  }
}

const stringContainer = new Container("hello");
const numberContainer = new Container(42);

// Classe avec contraintes
class Collection {
  private items: T[] = [];

  add(item: T): void {
    this.items.push(item);
  }

  findById(id: number): T | undefined {
    return this.items.find(item => item.id === id);
  }
}

interface User {
  id: number;
  name: string;
}

const users = new Collection();
users.add({ id: 1, name: "John" });

// Generic interfaces
interface Repository {
  getAll(): T[];
  getById(id: number): T | undefined;
  create(item: Omit): T;
  update(id: number, item: Partial): T;
  delete(id: number): void;
}

class UserRepository implements Repository {
  private users: User[] = [];

  getAll(): User[] {
    return this.users;
  }

  getById(id: number): User | undefined {
    return this.users.find(u => u.id === id);
  }

  create(data: Omit): User {
    const user: User = { id: Date.now(), ...data };
    this.users.push(user);
    return user;
  }

  update(id: number, data: Partial): User {
    const index = this.users.findIndex(u => u.id === id);
    this.users[index] = { ...this.users[index], ...data };
    return this.users[index];
  }

  delete(id: number): void {
    this.users = this.users.filter(u => u.id !== id);
  }
}

Types utilitaires

Partial, Required, Readonly

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

// Partial - Rend toutes propriétés optionnelles
type PartialUser = Partial;
// { id?: number; name?: string; email?: string; age?: number; }

function updateUser(id: number, updates: Partial) {
  // updates peut contenir n'importe quelle combinaison
}

updateUser(1, { name: "John" });
updateUser(1, { email: "john@example.com", age: 30 });

// Required - Rend toutes propriétés obligatoires
interface Config {
  host?: string;
  port?: number;
  timeout?: number;
}

type RequiredConfig = Required;
// { host: string; port: number; timeout: number; }

// Readonly - Rend toutes propriétés readonly
type ReadonlyUser = Readonly;

const user: ReadonlyUser = {
  id: 1,
  name: "John",
  email: "john@example.com",
  age: 30
};
// user.name = "Jane";  // Erreur

// Readonly avec arrays
type ReadonlyArray = readonly T[];

const numbers: ReadonlyArray = [1, 2, 3];
// numbers.push(4);  // Erreur
// numbers[0] = 10;  // Erreur

Pick, Omit, Extract, Exclude

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  role: "admin" | "user";
  createdAt: Date;
}

// Pick - Sélectionne propriétés
type UserPreview = Pick;
// { id: number; name: string; email: string; }

// Omit - Exclut propriétés
type UserWithoutPassword = Omit;
// Tous les champs sauf password

type PublicUser = Omit;

// Extract - Extrait types assignables à U
type Role = "admin" | "user" | "guest";
type AdminOrUser = Extract;
// "admin" | "user"

type Primitive = string | number | boolean | null | undefined;
type StringOrNumber = Extract;
// string | number

// Exclude - Exclut types assignables à U
type NonAdmin = Exclude;
// "user" | "guest"

type NonNullable = Exclude;
type Name = NonNullable;
// string

Record, Map types

// Record - Objet avec clés K et valeurs T
type PageInfo = Record;

const pages: PageInfo = {
  home: { title: "Home", url: "/" },
  about: { title: "About", url: "/about" },
  contact: { title: "Contact", url: "/contact" }
};

// Avec union de clés
type Page = "home" | "about" | "contact";
type PageData = Record;

// Dictionnaires typés
type UserRoles = Record;

const roles: UserRoles = {
  1: "admin",
  2: "user",
  3: "user"
};

// Map avec génériques
type ResponseMap = Record;

// Mapped types
type Nullable = {
  [P in keyof T]: T[P] | null;
};

type NullableUser = Nullable;
// Chaque propriété peut être null

// Mapped types avec modificateurs
type Mutable = {
  -readonly [P in keyof T]: T[P];
};

type Optional = {
  [P in keyof T]?: T[P];
};

type Concrete = {
  [P in keyof T]-?: T[P];
};

ReturnType, Parameters, ConstructorParameters

// ReturnType - Type de retour d'une fonction
function createUser(name: string, age: number) {
  return { name, age, id: Math.random() };
}

type User = ReturnType;
// { name: string; age: number; id: number; }

function getConfig() {
  return { host: "localhost", port: 3000 };
}

type Config = ReturnType;

// Parameters - Tuple des types de paramètres
type CreateUserParams = Parameters;
// [name: string, age: number]

function callWithParams(fn: typeof createUser, params: CreateUserParams) {
  return fn(...params);
}

// ConstructorParameters - Paramètres de constructeur
class Point {
  constructor(public x: number, public y: number) {}
}

type PointParams = ConstructorParameters;
// [x: number, y: number]

// InstanceType - Type d'instance d'une classe
type PointInstance = InstanceType;
// Point

// ThisParameterType - Type du paramètre this
function toHex(this: number) {
  return this.toString(16);
}

type Num = ThisParameterType;
// number

// OmitThisParameter - Retire this des paramètres
type ToHexFunction = OmitThisParameter;
// () => string

Awaited, Promise types

// Awaited - Type résolu d'une Promise
type Response = Awaited>;
// string

type NestedResponse = Awaited>>;
// number

async function fetchUser(): Promise {
  const response = await fetch("/api/user");
  return response.json();
}

type FetchedUser = Awaited>;
// User

// Avec génériques
type UnwrapPromise = T extends Promise ? U : T;

type A = UnwrapPromise>;  // string
type B = UnwrapPromise;           // string

// Utility pour async functions
type AsyncReturnType Promise> =
  T extends (...args: any) => Promise ? R : any;

type UserData = AsyncReturnType;
// User

Types conditionnels

Syntaxe de base

// Conditional type: T extends U ? X : Y
type IsString = T extends string ? true : false;

type A = IsString;   // true
type B = IsString;   // false

// Avec génériques
type TypeName =
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends undefined ? "undefined" :
  T extends Function ? "function" :
  "object";

type T0 = TypeName;    // "string"
type T1 = TypeName;    // "number"
type T2 = TypeName<() => void>; // "function"

// Infer keyword
type ReturnType = T extends (...args: any[]) => infer R ? R : any;

type Func = (x: string) => number;
type Return = ReturnType;  // number

// Infer avec arrays
type Flatten = T extends Array ? U : T;

type Str = Flatten;    // string
type Num = Flatten;      // number

// Infer avec Promises
type UnwrapPromise = T extends Promise ? U : T;

type P = UnwrapPromise>;  // string

// Nested infer
type DeepFlatten = T extends Array
  ? DeepFlatten
  : T;

type Deep = DeepFlatten;  // number

Types conditionnels distribués

// Distributive conditional types
type ToArray = T extends any ? T[] : never;

type StrOrNum = ToArray;
// string[] | number[] (distribué)

// Non-distributive (avec tuple)
type ToArrayNonDist = [T] extends [any] ? T[] : never;

type Combined = ToArrayNonDist;
// (string | number)[]

// Filtrer types null/undefined
type NonNullable = T extends null | undefined ? never : T;

type MaybeString = string | null | undefined;
type DefiniteString = NonNullable;  // string

// Extraire types de fonction
type FunctionPropertyNames = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

interface User {
  id: number;
  name: string;
  save(): void;
  delete(): void;
}

type UserMethods = FunctionPropertyNames;
// "save" | "delete"

// Construire type depuis méthodes
type MethodsOnly = Pick>;

type UserMethodsType = MethodsOnly;
// { save(): void; delete(): void; }

Exemples avancés

// Deep Readonly
type DeepReadonly = {
  readonly [P in keyof T]: T[P] extends object
    ? DeepReadonly
    : T[P];
};

interface Config {
  database: {
    host: string;
    port: number;
    credentials: {
      user: string;
      password: string;
    };
  };
}

type ReadonlyConfig = DeepReadonly;
// Tous les niveaux sont readonly

// Deep Partial
type DeepPartial = {
  [P in keyof T]?: T[P] extends object
    ? DeepPartial
    : T[P];
};

// Require certain properties
type RequireKeys = T & Required>;

interface User {
  id?: number;
  name?: string;
  email?: string;
}

type UserWithId = RequireKeys;
// id est required, name et email optional

// Make certain properties optional
type PartialKeys =
  Omit & Partial>;

interface Product {
  id: number;
  name: string;
  price: number;
}

type ProductUpdate = PartialKeys;
// id required, name et price optional

// Function type helper
type Promisify = T extends (...args: infer A) => infer R
  ? (...args: A) => Promise
  : never;

type SyncFn = (x: number, y: string) => boolean;
type AsyncFn = Promisify;
// (x: number, y: string) => Promise

Template Literal Types

Bases

// String literal types
type World = "world";
type Greeting = hello ${World};
// "hello world"

// Unions
type EmailLocaleIDs = "welcomeemail" | "emailheading";
type FooterLocaleIDs = "footertitle" | "footersendoff";

type AllLocaleIDs = ${EmailLocaleIDs | FooterLocaleIDs}id;
// "welcomeemailid" | "emailheadingid" |
// "footertitleid" | "footersendoffid"

// Cartesian product
type Direction = "left" | "right" | "top" | "bottom";
type Margin = margin${Capitalize};
// "marginLeft" | "marginRight" | "marginTop" | "marginBottom"

type Padding = padding${Uppercase};
// "paddingLEFT" | "paddingRIGHT" | "paddingTOP" | "paddingBOTTOM"

Intrinsic String Manipulation Types

// Uppercase
type Loud = Uppercase<"hello">;  // "HELLO"

// Lowercase
type Quiet = Lowercase<"HELLO">;  // "hello"

// Capitalize
type Proper = Capitalize<"hello">;  // "Hello"

// Uncapitalize
type Lower = Uncapitalize<"Hello">;  // "hello"

// Combinaisons
type Action = "get" | "post" | "put" | "delete";
type Method = ${Uppercase}Request;
// "GETRequest" | "POSTRequest" | "PUTRequest" | "DELETERequest"

// Event handlers
type EventName = "click" | "focus" | "blur";
type Handler = on${Capitalize};
// "onClick" | "onFocus" | "onBlur"

// CSS properties
type CSSProperty = "color" | "background" | "border";
type CSSVar = --${CSSProperty};
// "--color" | "--background" | "--border"

Cas pratiques

// Routes typées
type HTTPMETHOD = "GET" | "POST" | "PUT" | "DELETE";
type Route = "/users" | "/products" | "/orders";

type APIEndpoint = ${HTTPMETHOD} ${Route};
// "GET /users" | "POST /users" | ... etc

// Database fields
type TableName = "users" | "posts" | "comments";
type Field = "id" | "createdat" | "updatedat";

type FullField = ${TableName}.${Field};
// "users.id" | "users.createdat" | "posts.id" | ...

// Query builder
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

type SortOrder = "asc" | "desc";
type SortKey = {
  [K in keyof T]: ${K & string}${SortOrder};
}[keyof T];

type UserSort = SortKey;
// "idasc" | "iddesc" | "nameasc" | "namedesc" | ...

// Event system
type EventMap = {
  click: { x: number; y: number };
  focus: { element: string };
  submit: { formData: FormData };
};

type EventName = keyof EventMap;
type EventHandler =
  (data: EventMap[E]) => void;

type EventHandlers = {
  [E in EventName as on${Capitalize}]: EventHandler;
};
// {
//   onClick: (data: { x: number; y: number }) => void;
//   onFocus: (data: { element: string }) => void;
//   onSubmit: (data: { formData: FormData }) => void;
// }

// Getters/Setters
type Getters = {
  [K in keyof T as get${Capitalize}]: () => T[K];
};

type Setters = {
  [K in keyof T as set${Capitalize}]: (value: T[K]) => void;
};

interface State {
  count: number;
  name: string;
}

type StateGetters = Getters;
// { getCount: () => number; getName: () => string; }

type StateSetters = Setters;
// { setCount: (value: number) => void; setName: (value: string) => void; }

Mapped Types avancés

Key Remapping

// Remapper clés
type RemoveId = {
  [K in keyof T as Exclude]: T[K];
};

interface User {
  id: number;
  name: string;
  email: string;
}

type UserWithoutId = RemoveId;
// { name: string; email: string; }

// Préfixer clés
type Prefix = {
  [K in keyof T as ${P}${K & string}]: T[K];
};

type PrefixedUser = Prefix">;
// { userid: number; username: string; useremail: string; }

// Filter keys
type FilterKeys = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

type StringPropsOnly = FilterKeys;
// { name: string; email: string; }

// Conditional key mapping
type OptionalProperties = {
  [K in keyof T as T[K] extends Required[K] ? never : K]: T[K];
};

interface Config {
  host: string;
  port?: number;
  timeout?: number;
}

type Optional = OptionalProperties;
// { port?: number; timeout?: number; }

Transformations complexes

// Nested object transformation
type PathsToStringProps = {
  [K in keyof T]: T[K] extends string
    ? [K]
    : T[K] extends object
    ? [K, ...PathsToStringProps]
    : never;
}[keyof T];

// Flatten nested types
type Flatten = T extends object
  ? { [K in keyof T]: Flatten }
  : T;

// Make nested nullable
type DeepNullable = {
  [K in keyof T]: T[K] extends object
    ? DeepNullable | null
    : T[K] | null;
};

// Mutable version
type Mutable = {
  -readonly [P in keyof T]: T[P] extends object
    ? Mutable
    : T[P];
};

// Type-safe omit
type StrictOmit = Pick>;

// Union to intersection
type UnionToIntersection =
  (U extends any ? (x: U) => void : never) extends
    (x: infer I) => void ? I : never;

type A = { a: string };
type B = { b: number };
type C = UnionToIntersection;
// { a: string } & { b: number }

Classes avec TypeScript

Syntaxe de base

// Classe simple
class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet(): string {
    return Hello, I'm ${this.name};
  }
}

// Modificateurs d'accès
class BankAccount {
  public owner: string;       // Accessible partout
  protected balance: number;  // Classe et sous-classes
  private pin: number;        // Seulement cette classe

  constructor(owner: string, balance: number, pin: number) {
    this.owner = owner;
    this.balance = balance;
    this.pin = pin;
  }

  public deposit(amount: number): void {
    this.balance += amount;
  }

  private validatePin(pin: number): boolean {
    return this.pin === pin;
  }
}

// Parameter properties (shorthand)
class User {
  constructor(
    public name: string,
    private age: number,
    readonly id: string
  ) {}
}

// Équivalent à:
class User2 {
  public name: string;
  private age: number;
  readonly id: string;

  constructor(name: string, age: number, id: string) {
    this.name = name;
    this.age = age;
    this.id = id;
  }
}

// Getters et setters
class Temperature {
  private celsius: number = 0;

  get fahrenheit(): number {
    return this.celsius  9/5 + 32;
  }

  set fahrenheit(value: number) {
    this.celsius = (value - 32)  5/9;
  }

  get celsius(): number {
    return this.celsius;
  }

  set celsius(value: number) {
    this.celsius = value;
  }
}

const temp = new Temperature();
temp.celsius = 25;
console.log(temp.fahrenheit);  // 77

Héritage et abstractions

// Héritage
class Animal {
  constructor(public name: string) {}

  move(distance: number = 0): void {
    console.log(${this.name} moved ${distance}m);
  }
}

class Dog extends Animal {
  bark(): void {
    console.log('Woof! Woof!');
  }

  // Override
  move(distance: number = 5): void {
    console.log('Running...');
    super.move(distance);
  }
}

// Classes abstraites
abstract class Shape {
  abstract getArea(): number;
  abstract getPerimeter(): number;

  // Méthode concrète
  describe(): string {
    return Area: ${this.getArea()}, Perimeter: ${this.getPerimeter()};
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super();
  }

  getArea(): number {
    return Math.PI  this.radius  2;
  }

  getPerimeter(): number {
    return 2  Math.PI  this.radius;
  }
}

class Rectangle extends Shape {
  constructor(
    private width: number,
    private height: number
  ) {
    super();
  }

  getArea(): number {
    return this.width  this.height;
  }

  getPerimeter(): number {
    return 2  (this.width + this.height);
  }
}

// Membres statiques
class MathUtils {
  static PI: number = 3.14159;

  static calculateCircleArea(radius: number): number {
    return this.PI  radius  2;
  }

  static max(...numbers: number[]): number {
    return Math.max(...numbers);
  }
}

console.log(MathUtils.PI);
console.log(MathUtils.max(1, 2, 3, 4, 5));

// Classe générique
class DataStore {
  private data: T[] = [];

  add(item: T): void {
    this.data.push(item);
  }

  get(index: number): T | undefined {
    return this.data[index];
  }

  getAll(): T[] {
    return this.data;
  }

  filter(predicate: (item: T) => boolean): T[] {
    return this.data.filter(predicate);
  }
}

interface Product {
  id: number;
  name: string;
  price: number;
}

const products = new DataStore();
products.add({ id: 1, name: "Laptop", price: 999 });

Décorateurs (Experimental)

// Activer dans tsconfig.json:
// "experimentalDecorators": true

// Class decorator
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
}

// Method decorator
function log(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;

  descriptor.value = function(...args: any[]) {
    console.log(Calling ${key} with:, args);
    const result = original.apply(this, args);
    console.log(Result:, result);
    return result;
  };

  return descriptor;
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

// Property decorator
function readonly(target: any, key: string) {
  Object.defineProperty(target, key, {
    writable: false
  });
}

class Person {
  @readonly
  name: string = "John";
}

// Parameter decorator
function required(target: any, key: string, index: number) {
  // Validation logic
}

class UserService {
  createUser(@required name: string, @required email: string) {
    // ...
  }
}

Modules et Namespaces

ES Modules

// export.ts
export interface User {
  id: number;
  name: string;
}

export class UserService {
  getUser(id: number): User {
    return { id, name: "John" };
  }
}

export function formatUser(user: User): string {
  return ${user.id}: ${user.name};
}

export const DEFAULTUSER: User = { id: 0, name: "Guest" };

// Default export
export default class ApiClient {
  baseUrl: string;
  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }
}

// import.ts
import ApiClient, { User, UserService, formatUser } from './export';
import  as UserModule from './export';
import type { User as UserType } from './export';  // Type-only import

// Re-export
export { User, UserService } from './export';
export  from './export';

Namespaces

// Namespace simple
namespace Validation {
  export interface StringValidator {
    isValid(s: string): boolean;
  }

  export class EmailValidator implements StringValidator {
    isValid(s: string): boolean {
      return /^[w-.]+@([w-]+.)+[w-]{2,4}$/.test(s);
    }
  }

  export class PhoneValidator implements StringValidator {
    isValid(s: string): boolean {
      return /^d{10}$/.test(s);
    }
  }
}

const emailValidator = new Validation.EmailValidator();

// Namespace imbriqués
namespace App {
  export namespace Models {
    export interface User {
      id: number;
      name: string;
    }
  }

  export namespace Services {
    export class UserService {
      getUser(id: number): Models.User {
        return { id, name: "John" };
      }
    }
  }
}

// Alias
import Models = App.Models;
import UserService = App.Services.UserService;

Types avancés React

Component Props

import { ReactNode, ComponentPropsWithoutRef } from 'react';

// Props basiques
interface ButtonProps {
  children: ReactNode;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

// Étendre props HTML natives
interface InputProps extends ComponentPropsWithoutRef<'input'> {
  label: string;
  error?: string;
}

function Input({ label, error, ...inputProps }: InputProps) {
  return (
    
{error && {error}}
); } // Polymorphic components type PolymorphicProps = { as?: E; children: ReactNode; } & Omit, 'as' | 'children'>; function Box({ as, children, ...props }: PolymorphicProps) { const Component = as || 'div'; return {children}; } // Utilisation Div par défaut {}}>Button Link // Event handlers typés interface FormProps { onSubmit: (data: FormData) => void; onChange: (field: string, value: string) => void; } // Generic component props interface ListProps { items: T[]; renderItem: (item: T) => ReactNode; keyExtractor: (item: T) => string | number; } function List({ items, renderItem, keyExtractor }: ListProps) { return (
    {items.map(item => (
  • {renderItem(item)}
  • ))}
); }

Hooks typés

import { useState, useEffect, useRef, useCallback } from 'react';

// useState avec type explicite
const [user, setUser] = useState(null);
const [items, setItems] = useState([]);

// useRef
const inputRef = useRef(null);
const timerRef = useRef(null);

// Custom hook typé
function useFetch(url: string) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;

    fetch(url)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) setData(data);
      })
      .catch(err => {
        if (!cancelled) setError(err);
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });

    return () => { cancelled = true; };
  }, [url]);

  return { data, loading, error };
}

// Utilisation
interface User {
  id: number;
  name: string;
}

const { data: user } = useFetch('/api/user');

// useCallback typé
const handleSubmit = useCallback((data: FormData) => {
  console.log(data);
}, []);

// Context typé
interface AuthContextType {
  user: User | null;
  login: (email: string, password: string) => Promise;
  logout: () => void;
}

const AuthContext = createContext(undefined);

function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
}

Déclarations de types

Declaration files (.d.ts)

// types.d.ts
declare module 'untyped-library' {
  export function doSomething(value: string): number;

  export interface Config {
    timeout: number;
    retries: number;
  }

  export class Client {
    constructor(config: Config);
    connect(): Promise;
    disconnect(): void;
  }
}

// Declare global
declare global {
  interface Window {
    myApp: {
      version: string;
      config: AppConfig;
    };
  }

  const APIURL: string;
}

// Ambient declarations
declare const VERSION: string;
declare function gtag(...args: any[]): void;

// Module augmentation
import 'express';

declare module 'express' {
  interface Request {
    user?: {
      id: string;
      email: string;
    };
  }
}

// Triple-slash directives
/// 
/// 

Types pour fichiers non-TS

// Pour CSS Modules
declare module '.module.css' {
  const classes: { [key: string]: string };
  export default classes;
}

// Pour images
declare module '.png' {
  const content: string;
  export default content;
}

declare module '.svg' {
  import { FC, SVGProps } from 'react';
  const content: FC>;
  export default content;
}

// Pour JSON
declare module '.json' {
  const value: any;
  export default value;
}

Bonnes pratiques

Conventions

// 1. Préférer interfaces pour objets publics
interface User {
  id: number;
  name: string;
}

// 2. Préférer types pour unions/intersections
type Status = 'pending' | 'active' | 'inactive';
type Result = Success & Timestamped;

// 3. Utiliser const assertions
const colors = ['red', 'green', 'blue'] as const;
type Color = typeof colors[number];  // 'red' | 'green' | 'blue'

// 4. Éviter any, préférer unknown
function process(value: unknown) {
  if (typeof value === 'string') {
    return value.toUpperCase();
  }
}

// 5. Utiliser strict mode
// tsconfig.json: "strict": true

// 6. Type guards
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    'name' in value
  );
}

// 7. Assertion functions
function assert(condition: unknown, message?: string): asserts condition {
  if (!condition) {
    throw new Error(message || 'Assertion failed');
  }
}

function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new Error('Not a string');
  }
}

// 8. Const enums pour performance
const enum Direction {
  Up,
  Down,
  Left,
  Right
}

// 9. Readonly par défaut
interface Config {
  readonly apiKey: string;
  readonly endpoints: readonly string[];
}

// 10. Utiliser types utilitaires
type UpdateUser = Partial;
type CreateUser = Omit;
type PublicUser = Pick;

Patterns communs

// Builder pattern
class QueryBuilder {
  private query: Partial = {};

  where(key: K, value: T[K]): this {
    this.query[key] = value;
    return this;
  }

  build(): Partial {
    return this.query;
  }
}

// Factory pattern
interface Product {
  id: string;
  name: string;
  price: number;
}

class ProductFactory {
  static create(type: 'book' | 'electronics'): Product {
    const id = Math.random().toString();
    switch (type) {
      case 'book':
        return { id, name: 'Book', price: 10 };
      case 'electronics':
        return { id, name: 'Laptop', price: 1000 };
    }
  }
}

// Singleton pattern
class Database {
  private static instance: Database;

  private constructor() {}

  static getInstance(): Database {
    if (!Database.instance) {
      Database.instance = new Database();
    }
    return Database.instance;
  }
}

// Observer pattern
type Listener = (data: T) => void;

class EventEmitter {
  private listeners: Listener[] = [];

  subscribe(listener: Listener): () => void {
    this.listeners.push(listener);
    return () => {
      this.listeners = this.listeners.filter(l => l !== listener);
    };
  }

  emit(data: T): void {
    this.listeners.forEach(listener => listener(data));
  }
}

Version: TypeScript 5.3+ | Ressources:* typescriptlang.org, type-challenges

Laisser un commentaire