Skip to main content

Méthodes et Fonctions TypeScript

Conventions de nommage

Fonctions et méthodes

  • camelCase pour les noms de fonctions et méthodes
  • Noms descriptifs indiquant l'action effectuée
  • Utiliser des verbes pour les actions, des prédicats pour les booléens
// ✅ Bonnes pratiques
function calculateTotalPrice(items: CartItem[]): number { }
function validateUserInput(input: string): boolean { }
function fetchUserProfile(userId: string): Promise<UserProfile> { }

class UserService {
createUser(userData: CreateUserData): Promise<User> { }
deleteUser(userId: string): Promise<void> { }
isUserActive(user: User): boolean { }
hasPermission(user: User, permission: string): boolean { }
}

// ❌ À éviter
function calc(items: any[]): number { } // Nom trop court
function process(input: any): any { } // Trop générique
function userData(id: string): Promise<any> { } // Nom sans verbe

Déclaration de fonctions

Fonctions nommées vs. expressions de fonction

  • Préférer les déclarations de fonction pour les fonctions de niveau supérieur
  • Utiliser les fonctions fléchées pour les expressions et callbacks
// ✅ Déclarations de fonction (niveau supérieur)
function processOrder(order: Order): ProcessedOrder {
return {
id: order.id,
status: 'processed',
processedAt: new Date()
};
}

// ✅ Fonctions fléchées (expressions et callbacks)
const items = orders.map(order => processOrder(order));

const eventHandler = (event: MouseEvent): void => {
event.preventDefault();
// Handle event
};

// ✅ Fonctions fléchées pour les méthodes courtes
class Calculator {
add = (a: number, b: number): number => a + b;
multiply = (a: number, b: number): number => a * b;
}

Annotations de type

  • Toujours annoter les paramètres
  • Annoter le type de retour pour les fonctions publiques
  • Laisser l'inférence pour les fonctions privées simples
// ✅ Fonction publique avec types explicites
export function validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}

// ✅ Fonction avec type de retour complexe
function groupUsersByRole(users: User[]): Record<string, User[]> {
return users.reduce((groups, user) => {
const role = user.role;
if (!groups[role]) {
groups[role] = [];
}
groups[role].push(user);
return groups;
}, {} as Record<string, User[]>);
}

// ✅ Fonction async avec gestion d'erreur
async function fetchWithRetry<T>(
url: string,
options: RequestInit = {},
maxRetries: number = 3
): Promise<T> {
let lastError: Error;

for (let i = 0; i <= maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
lastError = error as Error;
if (i < maxRetries) {
await delay(Math.pow(2, i) * 1000); // Exponential backoff
}
}
}

throw lastError!;
}

Paramètres de fonction

Paramètres optionnels et par défaut

  • Placer les paramètres optionnels en fin de liste
  • Utiliser des valeurs par défaut plutôt que des unions avec undefined
// ✅ Paramètres avec valeurs par défaut
function createApiClient(
baseUrl: string,
timeout: number = 5000,
retries: number = 3
): ApiClient {
return new ApiClient({ baseUrl, timeout, retries });
}

// ✅ Paramètres optionnels
function formatDate(
date: Date,
locale?: string,
options?: Intl.DateTimeFormatOptions
): string {
return date.toLocaleDateString(locale, options);
}

// ✅ Destructuration avec valeurs par défaut
interface ApiOptions {
timeout?: number;
retries?: number;
headers?: Record<string, string>;
}

function makeApiCall(
url: string,
{ timeout = 5000, retries = 3, headers = {} }: ApiOptions = {}
): Promise<any> {
// Implementation
}

Rest parameters et spread

// ✅ Rest parameters pour un nombre variable d'arguments
function combineStrings(separator: string, ...strings: string[]): string {
return strings.join(separator);
}

// ✅ Fonction avec callback et rest parameters
function logWithContext(
level: 'info' | 'warn' | 'error',
message: string,
...metadata: any[]
): void {
console[level](message ...metadata);
}

// Utilisation
logWithContext('info', 'User logged in', { userId: 123, timestamp: new Date() });

Types de fonction

Interfaces de fonction

// ✅ Interface pour une fonction simple
interface Validator<T> {
(value: T): boolean;
}

const isPositiveNumber: Validator<number> = (value) => value > 0;
const isNonEmptyString: Validator<string> = (value) => value.length > 0;

// ✅ Interface pour une fonction avec propriétés
interface EventEmitter {
(event: string, ...args: any[]): void;
on(event: string, listener: Function): void;
off(event: string, listener: Function): void;
}

// ✅ Type de fonction avec génériques
type MapFunction<T, U> = (item: T, index: number, array: T[]) => U;
type FilterFunction<T> = (item: T, index: number, array: T[]) => boolean;

function processArray<T, U>(
array: T[],
mapFn: MapFunction<T, U>,
filterFn?: FilterFunction<U>
): U[] {
const mapped = array.map(mapFn);
return filterFn ? mapped.filter(filterFn) : mapped;
}

Surcharge de fonction

// ✅ Surcharges pour différents types d'entrée
function createElement(tag: 'input'): HTMLInputElement;
function createElement(tag: 'div'): HTMLDivElement;
function createElement(tag: 'button'): HTMLButtonElement;
function createElement(tag: string): HTMLElement;
function createElement(tag: string): HTMLElement {
return document.createElement(tag);
}

// ✅ Surcharges avec nombre différent de paramètres
function parseDate(dateString: string): Date;
function parseDate(year: number, month: number, day: number): Date;
function parseDate(
dateStringOrYear: string | number,
month?: number,
day?: number
): Date {
if (typeof dateStringOrYear === 'string') {
return new Date(dateStringOrYear);
} else {
return new Date(dateStringOrYear, month! - 1, day!);
}
}

Fonctions asynchrones

Async/await vs. Promises

  • Préférer async/await aux chaînes de .then()
  • Gérer les erreurs avec try/catch
  • Retourner des types Promise<T> explicites
// ✅ Fonction async avec gestion d'erreur
async function saveUserData(user: User): Promise<SaveResult> {
try {
// Validation des données
validateUser(user);

// Sauvegarde en base
const savedUser = await userRepository.save(user);

// Notification
await notificationService.notify({
type: 'user_created',
userId: savedUser.id
});

return {
success: true,
user: savedUser
};
} catch (error) {
logger.error('Failed to save user', { user, error });

return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}

// ✅ Fonction async avec plusieurs opérations parallèles
async function loadDashboardData(userId: string): Promise<DashboardData> {
try {
// Lancer plusieurs opérations en parallèle
const [user, notifications, stats] = await Promise.all([
userService.getUser(userId),
notificationService.getUnreadNotifications(userId),
analyticsService.getUserStats(userId)
]);

return {
user,
notifications,
stats,
loadedAt: new Date()
};
} catch (error) {
// Gestion centralisée des erreurs
throw new DashboardLoadError('Failed to load dashboard data', { userId, error });
}
}

Gestion des timeouts et annulation

// ✅ Fonction avec timeout
async function fetchWithTimeout<T>(
promise: Promise<T>,
timeoutMs: number
): Promise<T> {
const timeout = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error('Operation timed out')), timeoutMs);
});

return Promise.race([promise, timeout]);
}

// ✅ Fonction avec AbortController
async function fetchUserData(
userId: string,
signal?: AbortSignal
): Promise<User> {
const response = await fetch(`/api/users/${userId}`, {
signal,
headers: {
'Content-Type': 'application/json'
}
});

if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.statusText}`);
}

return response.json();
}

// Utilisation avec annulation
const controller = new AbortController();
const userPromise = fetchUserData('123', controller.signal);

// Annuler après 5 secondes
setTimeout(() => controller.abort(), 5000);

Méthodes de classe

Méthodes d'instance vs. statiques

class UserValidator {
private readonly emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

// ✅ Méthode d'instance (accès aux propriétés de l'instance)
validateEmail(email: string): boolean {
return this.emailRegex.test(email);
}

validateAge(age: number): boolean {
return age >= 0 && age <= 150;
}

// ✅ Méthode statique (utilitaire indépendant)
static isValidPassword(password: string): boolean {
return password.length >= 8 &&
/[A-Z]/.test(password) &&
/[a-z]/.test(password) &&
/\d/.test(password);
}

static generateTemporaryPassword(): string {
return Math.random().toString(36).slice(-12);
}
}

// Utilisation
const validator = new UserValidator();
const isEmailValid = validator.validateEmail('[email protected]');
const isPasswordValid = UserValidator.isValidPassword('MyPass123');

Méthodes privées et protégées

class DataProcessor {
private readonly cache = new Map<string, any>();

// ✅ Méthode publique (API de la classe)
public async processData(data: RawData): Promise<ProcessedData> {
const cacheKey = this.getCacheKey(data);

// Vérifier le cache
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}

// Traiter les données
const processed = await this.doProcessing(data);

// Mettre en cache
this.cache.set(cacheKey, processed);

return processed;
}

// ✅ Méthode privée (détail d'implémentation)
private getCacheKey(data: RawData): string {
return `${data.type}_${data.version}_${JSON.stringify(data.params)}`;
}

private async doProcessing(data: RawData): Promise<ProcessedData> {
// Validation
this.validateData(data);

// Transformation
const transformed = this.transformData(data);

// Enrichissement
return this.enrichData(transformed);
}

// ✅ Méthodes protégées (pour les classes dérivées)
protected validateData(data: RawData): void {
if (!data || !data.type) {
throw new Error('Invalid data: missing type');
}
}

protected transformData(data: RawData): TransformedData {
// Transformation de base
return {
...data,
transformedAt: new Date()
};
}

protected enrichData(data: TransformedData): ProcessedData {
// Enrichissement de base
return {
...data,
processedAt: new Date(),
version: '1.0'
};
}
}

Fonctions d'ordre supérieur

Fonctions qui retournent des fonctions

// ✅ Factory function
function createValidator<T>(
validationFn: (value: T) => boolean,
errorMessage: string
) {
return function validate(value: T): ValidationResult {
const isValid = validationFn(value);
return {
isValid,
error: isValid ? null : errorMessage
};
};
}

// Utilisation
const emailValidator = createValidator(
(email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
'Invalid email format'
);

const ageValidator = createValidator(
(age: number) => age >= 18 && age <= 120,
'Age must be between 18 and 120'
);

// ✅ Fonction de currying
function createApiCall(baseUrl: string) {
return function callEndpoint(endpoint: string) {
return function makeRequest<T>(options: RequestInit = {}): Promise<T> {
return fetch(`${baseUrl}${endpoint}`, options).then(res => res.json());
};
};
}

// Utilisation
const apiCall = createApiCall('https://api.example.com');
const getUsers = apiCall('/users');
const createUser = apiCall('/users');

const users = await getUsers<User[]>();
const newUser = await createUser<User>({ method: 'POST', body: userData });

Décorateurs de fonction

// ✅ Décorateur pour la mesure de performance
function measureTime(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;

descriptor.value = async function (...args: any[]) {
const start = performance.now();
try {
const result = await method.apply(this, args);
const end = performance.now();
console.log(`${propertyName} executed in ${end - start} milliseconds`);
return result;
} catch (error) {
const end = performance.now();
console.log(`${propertyName} failed after ${end - start} milliseconds`);
throw error;
}
};
}

// ✅ Décorateur pour la gestion d'erreur
function handleErrors(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;

descriptor.value = async function (...args: any[]) {
try {
return await method.apply(this, args);
} catch (error) {
console.error(`Error in ${propertyName}:`, error);
throw error;
}
};
}

class UserService {
@measureTime
@handleErrors
async createUser(userData: CreateUserData): Promise<User> {
// Implémentation de création d'utilisateur
return userRepository.create(userData);
}
}

Bonnes pratiques

1. Fonction pure vs. effet de bord

// ✅ Fonction pure (préférable)
function calculateTax(amount: number, rate: number): number {
return amount * rate;
}

function formatCurrency(amount: number, currency: string = 'EUR'): string {
return new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency
}).format(amount);
}

// ✅ Fonction avec effet de bord clairement identifié
function logAndCalculateTax(amount: number, rate: number): number {
const tax = calculateTax(amount, rate);
console.log(`Tax calculated: ${tax} for amount ${amount}`);
return tax;
}

2. Composition de fonctions

// ✅ Fonctions composables
const pipe = <T>(...fns: Array<(arg: T) => T>) => (value: T): T =>
fns.reduce((acc, fn) => fn(acc), value);

const compose = <T>(...fns: Array<(arg: T) => T>) => (value: T): T =>
fns.reduceRight((acc, fn) => fn(acc), value);

// Fonctions utilitaires
const trim = (str: string): string => str.trim();
const toLowerCase = (str: string): string => str.toLowerCase();
const removeSpaces = (str: string): string => str.replace(/\s/g, '');

// Composition
const normalizeString = pipe(trim, toLowerCase, removeSpaces);
const processInput = compose(removeSpaces, toLowerCase, trim);

// Utilisation
const cleanInput = normalizeString(' Hello World '); // "helloworld"

3. Gestion d'erreur robuste

// ✅ Type Result pour la gestion d'erreur fonctionnelle
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };

async function safeApiCall<T>(url: string): Promise<Result<T>> {
try {
const response = await fetch(url);
if (!response.ok) {
return {
success: false,
error: new Error(`HTTP ${response.status}: ${response.statusText}`)
};
}
const data = await response.json();
return { success: true, data };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error('Unknown error')
};
}
}

// Utilisation
const result = await safeApiCall<User[]>('/api/users');
if (result.success) {
console.log('Users:', result.data);
} else {
console.error('Failed to fetch users:', result.error.message);
}

4. Fonctions avec garde de type

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

function isNumber(value: unknown): value is number {
return typeof value === 'number' && !isNaN(value);
}

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

// ✅ Fonction de validation avec type narrowing
function validateAndProcessUser(data: unknown): User | null {
if (!isUser(data)) {
console.error('Invalid user data');
return null;
}

// TypeScript sait maintenant que data est un User
return {
...data,
name: data.name.trim(),
email: data.email.toLowerCase()
};
}