Перевантаження функцій у 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
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.