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 =; // "welcomeemailid" | "emailheadingid" | // "footertitleid" | "footersendoffid" // Cartesian product type Direction = "left" | "right" | "top" | "bottom"; type Margin =hello ${World}; // "hello world" // Unions type EmailLocaleIDs = "welcomeemail" | "emailheading"; type FooterLocaleIDs = "footertitle" | "footersendoff"; type AllLocaleIDs =${EmailLocaleIDs | FooterLocaleIDs}idmargin${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