Skip to main content
Практика завдань

Перевантаження функцій у TypeScript

Що таке перевантаження функцій?

Перевантаження функцій — це можливість оголошувати кілька сигнатур для однієї функції. TypeScript вибирає правильну сигнатуру на основі переданих аргументів.


Основний синтаксис

typescript
// Сигнатури перевантаження function greet(name: string): string; function greet(firstName: string, lastName: string): string; // Сигнатура реалізації function greet(firstName: string, lastName?: string): string { if (lastName) { return `Hello, ${firstName} ${lastName}!`; } return `Hello, ${firstName}!`; } // Використання greet('John'); // "Hello, John!" greet('John', 'Doe'); // "Hello, John Doe!" // greet('John', 'Doe', 'Jr.'); // Помилка: Очікується 1-2 аргументи

Чому використовувати перевантаження?

Різні типи повернення

typescript
function getValue(key: 'name'): string; function getValue(key: 'age'): number; function getValue(key: 'active'): boolean; function getValue(key: string): string | number | boolean { const data = { name: 'John', age: 30, active: true }; return data[key as keyof typeof data]; } const name = getValue('name'); // string const age = getValue('age'); // number const active = getValue('active'); // boolean

Опціональні параметри з різними типами

typescript
function makeDate(timestamp: number): Date; function makeDate(year: number, month: number, day: number): Date; function makeDate(yearOrTimestamp: number, month?: number, day?: number): Date { if (month !== undefined && day !== undefined) { return new Date(yearOrTimestamp, month, day); } return new Date(yearOrTimestamp); } const date1 = makeDate(1234567890); // Date const date2 = makeDate(2024, 11, 19); // Date // const date3 = makeDate(2024, 11); // Помилка

Порядок сигнатур має значення

TypeScript перевіряє сигнатури зверху вниз і використовує першу, що відповідає.

typescript
// Неправильно - загальна сигнатура спочатку function process(value: any): any; function process(value: string): string; function process(value: number): number; function process(value: any): any { return value; } const result = process('hello'); // any (не string!) // Правильно - від специфічної до загальної function process(value: string): string; function process(value: number): number; function process(value: any): any; function process(value: any): any { return value; } const result = process('hello'); // string

Сигнатура реалізації

Реалізація повинна бути сумісною з УСІМА сигнатурами перевантаження.

typescript
// Сигнатури перевантаження function combine(a: string, b: string): string; function combine(a: number, b: number): number; // Реалізація повинна покривати обидва випадки function combine(a: string | number, b: string | number): string | number { if (typeof a === 'string' && typeof b === 'string') { return a + b; } if (typeof a === 'number' && typeof b === 'number') { return a + b; } throw new Error('Invalid arguments'); } const str = combine('Hello, ', 'World'); // string const num = combine(10, 20); // number

Важливо:

Сигнатура реалізації НЕ видима зовнішньо. Користувачі бачать лише сигнатури перевантаження.


Перевантаження проти об'єднаних типів

Коли використовувати перевантаження

typescript
// З перевантаженнями - тип залежить від аргументу function parse(data: string): object; function parse(data: object): string; function parse(data: string | object): string | object { if (typeof data === 'string') { return JSON.parse(data); } return JSON.stringify(data); } const obj = parse('{"name":"John"}'); // object const str = parse({ name: 'John' }); // string

Коли достатньо об'єднаних типів

typescript
// Об'єднаний тип простіший і зрозуміліший function format(value: string | number): string { return value.toString(); }

Практичні приклади

Витяг масиву

typescript
function getFirst<T>(arr: T[]): T | undefined; function getFirst<T>(arr: T[], count: number): T[]; function getFirst<T>(arr: T[], count?: number): T | T[] | undefined { if (count === undefined) { return arr[0]; } return arr.slice(0, count); } const numbers = [1, 2, 3, 4, 5]; const first = getFirst(numbers); // number | undefined const firstThree = getFirst(numbers, 3); // number[]

Пошук елементів

typescript
function find(predicate: (item: string) => boolean): string | undefined; function find(predicate: (item: string) => boolean, all: true): string[]; function find( predicate: (item: string) => boolean, all?: boolean ): string | string[] | undefined { const items = ['apple', 'banana', 'cherry']; if (all) { return items.filter(predicate); } return items.find(predicate); } const one = find(item => item.startsWith('a')); // string | undefined const many = find(item => item.startsWith('a'), true); // string[]

Обробник подій

typescript
function addEventListener( element: HTMLElement, event: 'click', handler: (e: MouseEvent) => void ): void; function addEventListener( element: HTMLElement, event: 'keypress', handler: (e: KeyboardEvent) => void ): void; function addEventListener( element: HTMLElement, event: string, handler: (e: Event) => void ): void { element.addEventListener(event, handler); } const button = document.querySelector('button')!; addEventListener(button, 'click', (e) => { // e: MouseEvent console.log(e.clientX); }); addEventListener(button, 'keypress', (e) => { // e: KeyboardEvent console.log(e.key); });

Обгортка API

typescript
function request(method: 'GET', url: string): Promise<Response>; function request( method: 'POST' | 'PUT', url: string, body: object ): Promise<Response>; function request( method: string, url: string, body?: object ): Promise<Response> { const options: RequestInit = { method }; if (body) { options.body = JSON.stringify(body); options.headers = { 'Content-Type': 'application/json' }; } return fetch(url, options); } // Використання request('GET', '/api/users'); request('POST', '/api/users', { name: 'John' }); // request('POST', '/api/users'); // Помилка: тіло потрібне

Перевантаження методів класу

typescript
class DataStore { private data: Record<string, any> = {}; // Метод перевантаження get(key: 'name'): string; get(key: 'age'): number; get(key: 'active'): boolean; get(key: string): any { return this.data[key]; } set(key: 'name', value: string): void; set(key: 'age', value: number): void; set(key: 'active', value: boolean): void; set(key: string, value: any): void { this.data[key] = value; } } const store = new DataStore(); store.set('name', 'John'); // ОК store.set('age', 30); // ОК // store.set('age', 'thirty'); // Помилка const name = store.get('name'); // string const age = store.get('age'); // number

Перевантаження конструктора

typescript
class Point { x: number; y: number; constructor(x: number, y: number); constructor(coords: { x: number; y: number }); constructor(xOrCoords: number | { x: number; y: number }, y?: number) { if (typeof xOrCoords === 'number') { this.x = xOrCoords; this.y = y!; } else { this.x = xOrCoords.x; this.y = xOrCoords.y; } } } const p1 = new Point(10, 20); const p2 = new Point({ x: 10, y: 20 });

Генерики в перевантаженнях

typescript
function map<T, U>(arr: T[], fn: (item: T) => U): U[]; function map<T, U>(arr: T[], fn: (item: T, index: number) => U): U[]; function map<T, U>( arr: T[], fn: (item: T, index?: number) => U ): U[] { return arr.map(fn as any); } const numbers = [1, 2, 3]; const doubled = map(numbers, n => n * 2); const indexed = map(numbers, (n, i) => `${i}: ${n}`);

Загальні помилки

Несумісна реалізація

typescript
// Помилка: реалізація не сумісна з перевантаженнями function process(value: string): string; function process(value: number): number; function process(value: boolean): boolean { // Помилка! return value; }

Забування порядку

typescript
// Погано - загальний випадок приховує специфічні function handle(value: any): any; function handle(value: string): string; // Недосяжно! function handle(value: any): any { return value; }

Занадто багато перевантажень

typescript
// Погано - занадто складно function format(value: string): string; function format(value: number): string; function format(value: boolean): string; function format(value: Date): string; function format(value: object): string; // ... ще 10 перевантажень // Краще використовувати об'єднання або генерики function format(value: string | number | boolean | Date | object): string { return String(value); }

Найкращі практики

Використовуйте перевантаження лише коли це необхідно

typescript
// Перевантаження не потрібні function add(a: number, b: number): number { return a + b; } // Перевантаження потрібні - різні типи результатів function getValue(key: 'name'): string; function getValue(key: 'age'): number; function getValue(key: string): any { // ... }

Тримайте перевантаження близько до реалізації

typescript
// Добре - все разом function process(value: string): string; function process(value: number): number; function process(value: string | number): string | number { return value; }

Документуйте складні перевантаження

typescript
/** * Створює дату з мітки часу * @param timestamp - Unix мітка часу в мілісекундах */ function makeDate(timestamp: number): Date; /** * Створює дату з року, місяця та дня * @param year - Повний рік (наприклад, 2024) * @param month - Місяць (0-11) * @param day - День місяця (1-31) */ function makeDate(year: number, month: number, day: number): Date; function makeDate(yearOrTimestamp: number, month?: number, day?: number): Date { // ... }

Альтернативи перевантаженням

Умовні типи

typescript
type ReturnType<T> = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : never; function getValue<T extends 'string' | 'number' | 'boolean'>( type: T ): ReturnType<T> { // ... return null as any; }

Дискриміновані об'єднання

typescript
type Options = | { type: 'GET'; url: string } | { type: 'POST'; url: string; body: object }; function request(options: Options): Promise<Response> { // ... }

Висновок

Перевантаження функцій:

  • Дозволяють оголошувати кілька сигнатур для однієї функції
  • Вибір сигнатури залежить від аргументів
  • Порядок має значення (від специфічного до загального)
  • Реалізація повинна бути сумісною з усіма перевантаженнями
  • Корисні, коли тип повернення залежить від аргументів
  • Альтернативи: об'єднані типи, умовні типи, дискриміновані об'єднання
  • Не зловживайте — використовуйте лише коли це необхідно

На співбесідах:

Важливо вміти:

  • Пояснити синтаксис перевантаження функцій
  • Написати приклад з різними типами повернення
  • Пояснити різницю між сигнатурою перевантаження та реалізації
  • Показати важливість порядку сигнатур
  • Сказати, коли перевантаження краще за об'єднані типи
  • Назвати альтернативи (умовні типи, дискриміновані об'єднання)

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань