Skip to main content

Classes et Interfaces TypeScript

Conventions de nommage

Classes

  • PascalCase pour les noms de classes
  • Les noms doivent être descriptifs et refléter la responsabilité de la classe
class UserService {
// Propriétés et méthodes
}

class HttpClient {
// Propriétés et méthodes
}

class DatabaseConnection {
// Propriétés et méthodes
}

Interfaces

  • PascalCase pour les noms d'interfaces
  • Préférer les interfaces aux alias de type pour définir la forme des objets
  • Éviter le préfixe "I" devant les noms d'interfaces
interface User {
name: string;
id: number;
email?: string;
}

interface ApiResponse<T> {
data: T;
status: number;
message: string;
}

interface DatabaseConfig {
host: string;
port: number;
username: string;
password: string;
}

Déclaration des Classes

Propriétés de classe

  • Utiliser les modificateurs de visibilité appropriés (public, private, protected)
  • Préférer readonly pour les propriétés immutables
  • Initialiser les propriétés lors de leur déclaration quand c'est possible
class Employee {
// Propriétés publiques
public readonly id: number;
public name: string;

// Propriétés privées
private _salary: number = 0;

// Propriétés protégées
protected department: string;

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

Propriétés de paramètre (Parameter Properties)

  • Utiliser les propriétés de paramètre pour réduire le code répétitif
  • Préfixer les paramètres du constructeur avec les modificateurs de visibilité
class Student {
constructor(
public readonly studentId: string,
public firstName: string,
public lastName: string,
private _grade: number
) {
// Le constructeur est automatiquement simplifié
}

get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}

Implémentation d'Interfaces

Classes implémentant des interfaces

  • Utiliser implements pour s'assurer qu'une classe respecte un contrat
  • Une classe peut implémenter plusieurs interfaces
interface Serializable {
serialize(): string;
}

interface Comparable<T> {
compareTo(other: T): number;
}

class Product implements Serializable, Comparable<Product> {
constructor(
public readonly id: string,
public name: string,
public price: number
) {}

serialize(): string {
return JSON.stringify({
id: this.id,
name: this.name,
price: this.price
});
}

compareTo(other: Product): number {
return this.price - other.price;
}
}

Extension d'Interfaces

Héritage d'interface

  • Utiliser extends pour créer des interfaces spécialisées
  • Une interface peut étendre plusieurs interfaces
interface Animal {
name: string;
age: number;
}

interface Mammal extends Animal {
furColor: string;
isWarmBlooded: boolean;
}

interface Pet extends Animal {
owner: string;
isVaccinated: boolean;
}

// Interface combinant plusieurs héritages
interface Dog extends Mammal, Pet {
breed: string;
bark(): void;
}

Membres Statiques

Propriétés et méthodes statiques

  • Utiliser static pour les membres partagés entre toutes les instances
  • Éviter les noms conflictuels avec les propriétés Function (name, length)
class MathUtils {
static readonly PI = 3.14159;
static readonly E = 2.71828;

static calculateArea(radius: number): number {
return MathUtils.PI * radius * radius;
}

static max(a: number, b: number): number {
return a > b ? a : b;
}
}

// Utilisation
const area = MathUtils.calculateArea(5);
const maximum = MathUtils.max(10, 20);

Accesseurs (Getters/Setters)

Propriétés calculées et validation

  • Utiliser les getters pour les propriétés calculées
  • Utiliser les setters pour la validation des données
  • Les getters doivent être des fonctions pures (sans effets de bord)
class Rectangle {
private _width: number = 0;
private _height: number = 0;

get width(): number {
return this._width;
}

set width(value: number) {
if (value < 0) {
throw new Error('Width cannot be negative');
}
this._width = value;
}

get height(): number {
return this._height;
}

set height(value: number) {
if (value < 0) {
throw new Error('Height cannot be negative');
}
this._height = value;
}

// Propriété calculée en lecture seule
get area(): number {
return this._width * this._height;
}

get perimeter(): number {
return 2 * (this._width + this._height);
}
}

Héritage de Classes

Extension de classes

  • Utiliser extends pour créer des hiérarchies de classes
  • Appeler super() dans le constructeur des classes dérivées
  • Utiliser override pour clarifier les méthodes redéfinies
abstract class Vehicle {
protected constructor(
public readonly make: string,
public readonly model: string,
protected _speed: number = 0
) {}

abstract startEngine(): void;
abstract stopEngine(): void;

accelerate(increment: number): void {
this._speed += increment;
}

get currentSpeed(): number {
return this._speed;
}
}

class Car extends Vehicle {
constructor(
make: string,
model: string,
private _fuelLevel: number = 100
) {
super(make, model);
}

override startEngine(): void {
if (this._fuelLevel > 0) {
console.log(`${this.make} ${this.model} engine started`);
} else {
throw new Error('Cannot start: no fuel');
}
}

override stopEngine(): void {
this._speed = 0;
console.log(`${this.make} ${this.model} engine stopped`);
}

refuel(amount: number): void {
this._fuelLevel = Math.min(100, this._fuelLevel + amount);
}
}

Bonnes Pratiques

1. Préférer la composition à l'héritage

// Plutôt que l'héritage multiple (non supporté)
class Logger {
log(message: string): void {
console.log(`[${new Date().toISOString()}] ${message}`);
}
}

class UserService {
constructor(private logger: Logger) {}

createUser(userData: any): void {
// Logique de création
this.logger.log('User created successfully');
}
}

2. Utiliser des interfaces pour les contrats

interface Repository<T> {
findById(id: string): Promise<T | null>;
save(entity: T): Promise<T>;
delete(id: string): Promise<void>;
}

class UserRepository implements Repository<User> {
async findById(id: string): Promise<User | null> {
// Implémentation
return null;
}

async save(user: User): Promise<User> {
// Implémentation
return user;
}

async delete(id: string): Promise<void> {
// Implémentation
}
}

3. Éviter les classes vides

// ❌ Éviter
class Empty {}

// ✅ Préférer des interfaces ou types
interface Point {
x: number;
y: number;
}

type EventHandler = (event: Event) => void;

4. Utiliser des types d'union pour la flexibilité

interface LoadingState {
status: 'loading';
}

interface SuccessState {
status: 'success';
data: any;
}

interface ErrorState {
status: 'error';
error: string;
}

type ApiState = LoadingState | SuccessState | ErrorState;

class ApiClient {
private _state: ApiState = { status: 'loading' };

get state(): ApiState {
return this._state;
}

private setState(newState: ApiState): void {
this._state = newState;
}
}