Функции в 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 для функций, выполняющих побочный эффект.