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": ["node_modules", "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<number> = [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 = (<string>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<T> = (value: T) => boolean;
// Recursive types
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
type TreeNode<T> = {
value: T;
children?: TreeNode<T>[];
};
// Template literal types
type Greeting = `Hello ${string}`;
type EventName = `on${Capitalize<string>}`;
// 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<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("hello");
let output2 = identity(42); // Type inféré
// Array générique
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const first = firstElement([1, 2, 3]); // number | undefined
// Multiple type parameters
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const p = pair("hello", 42); // [string, number]
// Constraints (contraintes)
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(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<T, K extends keyof T>(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<T = string>(length: number, value: T): T[] {
return Array(length).fill(value);
}
const arr1 = createArray(3, "x"); // string[]
const arr2 = createArray<number>(3, 0); // number[]
Génériques avec classes
// Classe générique
class Container<T> {
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<T extends { id: number }> {
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<User>();
users.add({ id: 1, name: "John" });
// Generic interfaces
interface Repository<T> {
getAll(): T[];
getById(id: number): T | undefined;
create(item: Omit<T, 'id'>): T;
update(id: number, item: Partial<T>): T;
delete(id: number): void;
}
class UserRepository implements Repository<User> {
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, 'id'>): User {
const user: User = { id: Date.now(), ...data };
this.users.push(user);
return user;
}
update(id: number, data: Partial<User>): 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<T> - Rend toutes propriétés optionnelles
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number; }
function updateUser(id: number, updates: Partial<User>) {
// updates peut contenir n'importe quelle combinaison
}
updateUser(1, { name: "John" });
updateUser(1, { email: "john@example.com", age: 30 });
// Required<T> - Rend toutes propriétés obligatoires
interface Config {
host?: string;
port?: number;
timeout?: number;
}
type RequiredConfig = Required<Config>;
// { host: string; port: number; timeout: number; }
// Readonly<T> - Rend toutes propriétés readonly
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = {
id: 1,
name: "John",
email: "john@example.com",
age: 30
};
// user.name = "Jane"; // Erreur
// Readonly avec arrays
type ReadonlyArray<T> = readonly T[];
const numbers: ReadonlyArray<number> = [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<T, K> - Sélectionne propriétés
type UserPreview = Pick<User, "id" | "name" | "email">;
// { id: number; name: string; email: string; }
// Omit<T, K> - Exclut propriétés
type UserWithoutPassword = Omit<User, "password">;
// Tous les champs sauf password
type PublicUser = Omit<User, "password" | "createdAt">;
// Extract<T, U> - Extrait types assignables à U
type Role = "admin" | "user" | "guest";
type AdminOrUser = Extract<Role, "admin" | "user">;
// "admin" | "user"
type Primitive = string | number | boolean | null | undefined;
type StringOrNumber = Extract<Primitive, string | number>;
// string | number
// Exclude<T, U> - Exclut types assignables à U
type NonAdmin = Exclude<Role, "admin">;
// "user" | "guest"
type NonNullable<T> = Exclude<T, null | undefined>;
type Name = NonNullable<string | null | undefined>;
// string
Record, Map types
// Record<K, T> - Objet avec clés K et valeurs T
type PageInfo = Record<string, { title: string; url: string }>;
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<Page, { title: string; url: string }>;
// Dictionnaires typés
type UserRoles = Record<number, "admin" | "user">;
const roles: UserRoles = {
1: "admin",
2: "user",
3: "user"
};
// Map avec génériques
type ResponseMap<T> = Record<string, {
data: T;
status: number;
message: string;
}>;
// Mapped types
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
type NullableUser = Nullable<User>;
// Chaque propriété peut être null
// Mapped types avec modificateurs
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
type Optional<T> = {
[P in keyof T]?: T[P];
};
type Concrete<T> = {
[P in keyof T]-?: T[P];
};
ReturnType, Parameters, ConstructorParameters
// ReturnType<T> - Type de retour d'une fonction
function createUser(name: string, age: number) {
return { name, age, id: Math.random() };
}
type User = ReturnType<typeof createUser>;
// { name: string; age: number; id: number; }
function getConfig() {
return { host: "localhost", port: 3000 };
}
type Config = ReturnType<typeof getConfig>;
// Parameters<T> - Tuple des types de paramètres
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, age: number]
function callWithParams(fn: typeof createUser, params: CreateUserParams) {
return fn(...params);
}
// ConstructorParameters<T> - Paramètres de constructeur
class Point {
constructor(public x: number, public y: number) {}
}
type PointParams = ConstructorParameters<typeof Point>;
// [x: number, y: number]
// InstanceType<T> - Type d'instance d'une classe
type PointInstance = InstanceType<typeof Point>;
// Point
// ThisParameterType<T> - Type du paramètre this
function toHex(this: number) {
return this.toString(16);
}
type Num = ThisParameterType<typeof toHex>;
// number
// OmitThisParameter<T> - Retire this des paramètres
type ToHexFunction = OmitThisParameter<typeof toHex>;
// () => string
Awaited, Promise types
// Awaited<T> - Type résolu d'une Promise
type Response = Awaited<Promise<string>>;
// string
type NestedResponse = Awaited<Promise<Promise<number>>>;
// number
async function fetchUser(): Promise<User> {
const response = await fetch("/api/user");
return response.json();
}
type FetchedUser = Awaited<ReturnType<typeof fetchUser>>;
// User
// Avec génériques
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<string>; // string
// Utility pour async functions
type AsyncReturnType<T extends (...args: any) => Promise<any>> =
T extends (...args: any) => Promise<infer R> ? R : any;
type UserData = AsyncReturnType<typeof fetchUser>;
// User
Types conditionnels
Syntaxe de base
// Conditional type: T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// Avec génériques
type TypeName<T> =
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>; // "string"
type T1 = TypeName<number>; // "number"
type T2 = TypeName<() => void>; // "function"
// Infer keyword
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
type Func = (x: string) => number;
type Return = ReturnType<Func>; // number
// Infer avec arrays
type Flatten<T> = T extends Array<infer U> ? U : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
// Infer avec Promises
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type P = UnwrapPromise<Promise<string>>; // string
// Nested infer
type DeepFlatten<T> = T extends Array<infer U>
? DeepFlatten<U>
: T;
type Deep = DeepFlatten<number[][][]>; // number
Types conditionnels distribués
// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;
type StrOrNum = ToArray<string | number>;
// string[] | number[] (distribué)
// Non-distributive (avec tuple)
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Combined = ToArrayNonDist<string | number>;
// (string | number)[]
// Filtrer types null/undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>; // string
// Extraire types de fonction
type FunctionPropertyNames<T> = {
[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<User>;
// "save" | "delete"
// Construire type depuis méthodes
type MethodsOnly<T> = Pick<T, FunctionPropertyNames<T>>;
type UserMethodsType = MethodsOnly<User>;
// { save(): void; delete(): void; }
Exemples avancés
// Deep Readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
interface Config {
database: {
host: string;
port: number;
credentials: {
user: string;
password: string;
};
};
}
type ReadonlyConfig = DeepReadonly<Config>;
// Tous les niveaux sont readonly
// Deep Partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
// Require certain properties
type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;
interface User {
id?: number;
name?: string;
email?: string;
}
type UserWithId = RequireKeys<User, "id">;
// id est required, name et email optional
// Make certain properties optional
type PartialKeys<T, K extends keyof T> =
Omit<T, K> & Partial<Pick<T, K>>;
interface Product {
id: number;
name: string;
price: number;
}
type ProductUpdate = PartialKeys<Product, "name" | "price">;
// id required, name et price optional
// Function type helper
type Promisify<T> = T extends (...args: infer A) => infer R
? (...args: A) => Promise<R>
: never;
type SyncFn = (x: number, y: string) => boolean;
type AsyncFn = Promisify<SyncFn>;
// (x: number, y: string) => Promise<boolean>
Template Literal Types
Bases
// String literal types
type World = "world";
type Greeting = `hello ${World}`;
// "hello world"
// Unions
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// "welcome_email_id" | "email_heading_id" |
// "footer_title_id" | "footer_sendoff_id"
// Cartesian product
type Direction = "left" | "right" | "top" | "bottom";
type Margin = `margin${Capitalize<Direction>}`;
// "marginLeft" | "marginRight" | "marginTop" | "marginBottom"
type Padding = `padding${Uppercase<Direction>}`;
// "paddingLEFT" | "paddingRIGHT" | "paddingTOP" | "paddingBOTTOM"
Intrinsic String Manipulation Types
// Uppercase<T>
type Loud = Uppercase<"hello">; // "HELLO"
// Lowercase<T>
type Quiet = Lowercase<"HELLO">; // "hello"
// Capitalize<T>
type Proper = Capitalize<"hello">; // "Hello"
// Uncapitalize<T>
type Lower = Uncapitalize<"Hello">; // "hello"
// Combinaisons
type Action = "get" | "post" | "put" | "delete";
type Method = `${Uppercase<Action>}_Request`;
// "GET_Request" | "POST_Request" | "PUT_Request" | "DELETE_Request"
// Event handlers
type EventName = "click" | "focus" | "blur";
type Handler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"
// CSS properties
type CSSProperty = "color" | "background" | "border";
type CSSVar = `--${CSSProperty}`;
// "--color" | "--background" | "--border"
Cas pratiques
// Routes typées
type HTTP_METHOD = "GET" | "POST" | "PUT" | "DELETE";
type Route = "/users" | "/products" | "/orders";
type APIEndpoint = `${HTTP_METHOD} ${Route}`;
// "GET /users" | "POST /users" | ... etc
// Database fields
type TableName = "users" | "posts" | "comments";
type Field = "id" | "created_at" | "updated_at";
type FullField = `${TableName}.${Field}`;
// "users.id" | "users.created_at" | "posts.id" | ...
// Query builder
interface User {
id: number;
name: string;
email: string;
age: number;
}
type SortOrder = "asc" | "desc";
type SortKey<T> = {
[K in keyof T]: `${K & string}_${SortOrder}`;
}[keyof T];
type UserSort = SortKey<User>;
// "id_asc" | "id_desc" | "name_asc" | "name_desc" | ...
// Event system
type EventMap = {
click: { x: number; y: number };
focus: { element: string };
submit: { formData: FormData };
};
type EventName = keyof EventMap;
type EventHandler<E extends EventName> =
(data: EventMap[E]) => void;
type EventHandlers = {
[E in EventName as `on${Capitalize<E>}`]: EventHandler<E>;
};
// {
// onClick: (data: { x: number; y: number }) => void;
// onFocus: (data: { element: string }) => void;
// onSubmit: (data: { formData: FormData }) => void;
// }
// Getters/Setters
type Getters<T> = {
[K in keyof T as `get${Capitalize<K & string>}`]: () => T[K];
};
type Setters<T> = {
[K in keyof T as `set${Capitalize<K & string>}`]: (value: T[K]) => void;
};
interface State {
count: number;
name: string;
}
type StateGetters = Getters<State>;
// { getCount: () => number; getName: () => string; }
type StateSetters = Setters<State>;
// { setCount: (value: number) => void; setName: (value: string) => void; }
Mapped Types avancés
Key Remapping
// Remapper clés
type RemoveId<T> = {
[K in keyof T as Exclude<K, "id">]: T[K];
};
interface User {
id: number;
name: string;
email: string;
}
type UserWithoutId = RemoveId<User>;
// { name: string; email: string; }
// Préfixer clés
type Prefix<T, P extends string> = {
[K in keyof T as `${P}${K & string}`]: T[K];
};
type PrefixedUser = Prefix<User, "user_">;
// { user_id: number; user_name: string; user_email: string; }
// Filter keys
type FilterKeys<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
type StringPropsOnly = FilterKeys<User, string>;
// { name: string; email: string; }
// Conditional key mapping
type OptionalProperties<T> = {
[K in keyof T as T[K] extends Required<T>[K] ? never : K]: T[K];
};
interface Config {
host: string;
port?: number;
timeout?: number;
}
type Optional = OptionalProperties<Config>;
// { port?: number; timeout?: number; }
Transformations complexes
// Nested object transformation
type PathsToStringProps<T> = {
[K in keyof T]: T[K] extends string
? [K]
: T[K] extends object
? [K, ...PathsToStringProps<T[K]>]
: never;
}[keyof T];
// Flatten nested types
type Flatten<T> = T extends object
? { [K in keyof T]: Flatten<T[K]> }
: T;
// Make nested nullable
type DeepNullable<T> = {
[K in keyof T]: T[K] extends object
? DeepNullable<T[K]> | null
: T[K] | null;
};
// Mutable version
type Mutable<T> = {
-readonly [P in keyof T]: T[P] extends object
? Mutable<T[P]>
: T[P];
};
// Type-safe omit
type StrictOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// Union to intersection
type UnionToIntersection<U> =
(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 | B>;
// { 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<T> {
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<Product>();
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 DEFAULT_USER: 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 (
<div>
<label>{label}</label>
<input {...inputProps} />
{error && <span>{error}</span>}
</div>
);
}
// Polymorphic components
type PolymorphicProps<E extends React.ElementType> = {
as?: E;
children: ReactNode;
} & Omit<ComponentPropsWithoutRef<E>, 'as' | 'children'>;
function Box<E extends React.ElementType = 'div'>({
as,
children,
...props
}: PolymorphicProps<E>) {
const Component = as || 'div';
return <Component {...props}>{children}</Component>;
}
// Utilisation
<Box>Div par défaut</Box>
<Box as="button" onClick={() => {}}>Button</Box>
<Box as="a" href="/link">Link</Box>
// Event handlers typés
interface FormProps {
onSubmit: (data: FormData) => void;
onChange: (field: string, value: string) => void;
}
// Generic component props
interface ListProps<T> {
items: T[];
renderItem: (item: T) => ReactNode;
keyExtractor: (item: T) => string | number;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>
{renderItem(item)}
</li>
))}
</ul>
);
}
Hooks typés
import { useState, useEffect, useRef, useCallback } from 'react';
// useState avec type explicite
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);
// useRef
const inputRef = useRef<HTMLInputElement>(null);
const timerRef = useRef<NodeJS.Timeout | null>(null);
// Custom hook typé
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(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<User>('/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<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(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<void>;
disconnect(): void;
}
}
// Declare global
declare global {
interface Window {
myApp: {
version: string;
config: AppConfig;
};
}
const API_URL: 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
/// <reference types="node" />
/// <reference path="./custom.d.ts" />
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<SVGProps<SVGSVGElement>>;
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<User>;
type CreateUser = Omit<User, 'id'>;
type PublicUser = Pick<User, 'id' | 'name'>;
Patterns communs
// Builder pattern
class QueryBuilder<T> {
private query: Partial<T> = {};
where<K extends keyof T>(key: K, value: T[K]): this {
this.query[key] = value;
return this;
}
build(): Partial<T> {
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<T> = (data: T) => void;
class EventEmitter<T> {
private listeners: Listener<T>[] = [];
subscribe(listener: Listener<T>): () => 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