Функції в TypeScript — це громадяни першого класу (First-Class Citizens). Вони можуть передаватися як аргументи, повертатися з інших функцій та присвоюватися змінним.
Типізація функції є формальним математичним контрактом:
f: (A1, A2, ...An) -> R
Контракт визначає набір вхідних параметрів та гарантію типу значення, що повертається.
На відміну від багатьох статично типізованих мов, функції в TypeScript мають унікальні властивості:
strictFunctionTypes.void vs undefined vs never): розділення між функціями без корисного результату, поверненням undefined та функціями, які ніколи не повертають керування (нескінченні цикли, викидання винятків).Погляньмо на відмінності між void, undefined та never:
+-------------------------------------------------------------------------+
| СЕМАНТИКА ПОВЕРНЕННЯ: VOID vs UNDEFINED vs NEVER |
+-------------------------------------------------------------------------+
| void: |
| "Мені не важливий результат". Функція виконує побічний ефект. |
| У JS повернеться undefined, але TypeScript дозволяє колбекам повертати |
| будь-яке значення, ігноруючи його з боку, що викликає. |
+-------------------------------------------------------------------------+
| undefined: |
| "Функція явно повертає значення undefined". |
| Обов'язковий return undefined або порожній return. |
+-------------------------------------------------------------------------+
| never: |
| "Функція НІКОЛИ не завершиться нормально". |
| Або викидає помилку throw new Error(), або зависає в циклі while. |
+-------------------------------------------------------------------------+Функція, що викликає, може передавати менше аргументів, ніж оголошено, але не більше необхідного (arity check).
Кожен параметр цільової функції зобов'язаний безпечно приймати тип відповідного аргументу вихідної функції.
Тип результату вихідної функції зобов'язаний бути підтипом оголошеного типу повернення цільової сигнатури.
Компілятор звіряє аргументи виклику з перевантаженнями зверху вниз до першого повного збігу.
Чому компілятор дозволяє коду `const fn: () => void = () => 42;` успішно пройти перевірку типів, незважаючи на те, що стрілкова функція повертає число?
(event: React.MouseEvent<HTMLButtonElement>) => void.async function fetchEntity(): Promise<Entity> для захисту від неявного Promise<unknown>.readFile(path, 'utf-8'): string vs readFile(path): Buffer).Сигнатуру функції можна оголосити у вигляді стрілкового типу або в тілі інтерфейсу:
// 1. Псевдонім стрілкового типу:
type FormValidator = (value: string, minLength?: number) => boolean;
// 2. Об'єктний тип із сигнатурою виклику (Call Signature):
interface Logger {
// Сигнатура виклику
(message: string, level?: 'info' | 'warn' | 'error'): void;
// Статична властивість, прикріплена до функції
defaultLevel: 'info' | 'warn' | 'error';
}
const consoleLogger: Logger = Object.assign(
(msg: string, level: 'info' | 'warn' | 'error' = 'info') => {
console.log(`[${level.toUpperCase()}] ${msg}`);
},
{ defaultLevel: 'info' as const }
);Перевантаження складається з одного або кількох заголовків перевантаження (без тіла) та однієї сигнатури реалізації (з тілом):
// Публічні перевантаження (видимі споживачам API)
function formatTimestamp(timestamp: number): string;
function formatTimestamp(date: Date, locale: string): string;
// Сигнатура реалізації (НЕ видима споживачам безпосередньо!)
function formatTimestamp(arg1: number | Date, arg2?: string): string {
if (typeof arg1 === 'number') {
return new Date(arg1).toISOString();
}
return arg1.toLocaleDateString(arg2 ?? 'en-US');
}
// Коректні виклики:
formatTimestamp(1728000000000); // OK
formatTimestamp(new Date(), 'ru-RU'); // OK
// ПОМИЛКА: компілятор звіряє лише з публічними перевантаженнями, а не реалізацією!
// formatTimestamp(new Date()); // TS2554: Expected 2 arguments, but got 1.Опціональні параметри (param?: Type) зобов'язані слідувати строго після обов'язкових параметрів:
// Дефолтні параметри автоматично роблять параметр опціональним
function createRequest(
url: string,
timeoutMs: number = 5000,
headers?: Record<string, string>
) {
return { url, timeoutMs, headers: headers ?? {} };
}
// Типізовані Rest-параметри з використанням кортежів:
type LogArgs = [code: number, message: string, ...details: unknown[]];
function emitAuditLog(...args: LogArgs): void {
const [code, message, ...details] = args;
console.log(`Audit #${code}: ${message}`, details);
}
emitAuditLog(403, 'Forbidden', { ip: '127.0.0.1' }); // КоректноЧому код, що викликає, не може викликати функцію з аргументами, що підходять під сигнатуру реалізації, якщо такої комбінації немає в публічних перевантаженнях?
TypeScript зіставляє перевантаження зверху вниз. Якщо поставити ширшу сигнатуру першою, вона перехопить усі виклики:
// ПОМИЛКА: перше перевантаження заблокує друге!
function get(url: string): unknown;
function get(url: 'https://api.wpskills.dev'): SpecificResponse;
// ПРАВИЛЬНО: розташовуйте специфічні сигнатури ПЕРШИМИ, а широкі — останніми!
function get(url: 'https://api.wpskills.dev'): SpecificResponse;
function get(url: string): unknown;Оголошення функції з never, якщо вона завершується звичайним чином:
// ПОМИЛКА: TS2534: A function returning 'never' cannot have a reachable end point.
function logError(msg: string): never {
console.error(msg);
// Немає throw або while(true)!
}Використовуйте void для функцій, що виконують побічний ефект.