TypeScript / 39 минут

Типизация функций

Опишите параметры, результаты, callbacks и допустимые формы вызова.

В процессе Теория: впереди Практика: впереди Тест: впереди
04 / 12
После этой главы
  • Опишите параметры, результаты, callbacks и допустимые формы вызова.
  • Применить тему в задаче «Типизация функций» и объяснить граничные случаи.

Около 9 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.

Сигнатура как договорённость о вызове

Тип функции описывает допустимые параметры и результат, но не обещает конкретный алгоритм внутри. Это позволяет передавать реализацию как зависимость: форматирование, сравнение, получение времени или чтение данных. Хорошая сигнатура сохраняет смысл связи входа и выхода, чтобы потребителю не приходилось использовать any и угадывать результат каждого вызова.

Аннотацию можно записать у объявления функции или отдельно у переменной, хранящей функцию. В контекстно типизированном callback TypeScript часто выводит параметры из ожидаемого типа. Используйте этот вывод, когда он делает код яснее; явное объявление полезно для экспортируемого контракта и сложных случаев совместимости.

TypeScript
type Formatter = (name: string) => string;
const upper: Formatter = name => name.toUpperCase();
function printName(name: string, format: Formatter): string {
  return format(name);
}

Optional, default и rest

Необязательный параметр может отсутствовать, поэтому внутри функции его тип учитывает undefined. Параметр с начальным значением получает правило замены при отсутствии или undefined. null и пустая строка этим правилом автоматически не заменяются. Не проектируйте длинную цепочку необязательных позиционных параметров, если объект options сделает вызов заметно понятнее.

TypeScript
function greeting(name = 'Гость', prefix?: string): string {
  return `${prefix ?? 'Привет'}, ${name}`;
}
function total(...values: number[]): number {
  return values.reduce((sum, value) => sum + value, 0);
}

Rest-параметр описывается массивом или кортежем подходящей формы и должен быть последним. Он не означает, что все входные данные уже проверены во время исполнения. Сигнатура защищает проверяемые TypeScript-вызовы, но HTTP-клиент может отправить что угодно. Это разделение остаётся актуальным при любом количестве аннотаций.

Возвращаемое значение, void и never

number или string задают конкретный тип результата. void используют для контракта, где вызывающий код не должен опираться на возвращённое значение. Это особенно часто встречается у callback для побочного эффекта. never обозначает отсутствие нормального возврата, например функцию, которая всегда бросает исключение. Эти типы не следует заменять друг другом как синонимы «ничего».

TypeScript
function fail(message: string): never { throw new Error(message); }
function notify(message: string): void { console.log(message); }
async function calculate(): Promise<number> { return 42; }

Async-функция возвращает Promise результата. Если забыть дождаться её в вызывающем коде, статическая проверка часто помогает увидеть несоответствие Promise<number> и number. Но отклонение Promise остаётся runtime-сценарием, который нужно обработать соответствующим способом.

Перегрузки и связь вариантов

Перегрузки описывают несколько допустимых форм вызова одной реализации. Они полезны, когда форма результата зависит от формы аргументов и обычный union теряет важную связь. Реализация должна корректно обслуживать все заявленные сигнатуры, но её широкая внутренняя сигнатура не становится дополнительным публичным вариантом сама по себе.

TypeScript
function size(value: string): number;
function size(value: string[]): number;
function size(value: string | string[]): number {
  return value.length;
}

Не используйте перегрузки там, где простая сигнатура с union полностью выражает контракт. Избыточные формы повышают стоимость изменения и затрудняют чтение сообщений об ошибках. Если важно сохранить тип элемента между несколькими аргументами и результатом, возможно, нужен generic, который подробно разбирается в следующем модуле.

Совместимость callbacks

Callback получает аргументы от вызывающего API, а не от ваших ожиданий. Например, map передаёт элемент, индекс и массив. Функция, переданная напрямую, может интерпретировать дополнительные аргументы иначе, чем вы предполагали. Известный пример — parseInt, которому второй аргумент задаёт систему счисления. Короткая обёртка иногда делает смысл безопаснее и очевиднее.

TypeScript
const values = ['10', '10', '10'];
const numbers = values.map(value => parseInt(value, 10));

Также различайте «callback может быть вызван без аргумента» и «callback может проигнорировать переданный аргумент». Не делайте параметр optional только потому, что часть реализаций его не использует. Optional описывает, что разрешено вызывающей стороне, и может добавить ненужный undefined в каждую реализацию.

Практика форматирования

В задаче нужен callback с явным типом (name:string) => string. Он использует prefix из внешнего окружения и применяется к массиву через map. Это соединяет статический контракт функции с уже изученным замыканием JavaScript. TypeScript не меняет принцип создания окружений и не выполняет map за разработчика.

Проверьте пустой массив, пустое имя и префикс на кириллице. Возвращаемый тип solve — string[], поэтому вызов forEach без отдельного результата не подойдёт. После решения вынесите Formatter в отдельный type alias и добавьте две независимые реализации. Потребитель должен работать с обеими через один понятный контракт без принудительных утверждений типов.

Загружаем редактор…

Музыка: Reverie — Scott Buckley · CC BY 4.0