Тип функции описывает её параметры и возвращаемое значение и записывается через стрелку: (a: number, b: number) => number. Дайте ему имя через type, и его можно использовать для переменных, параметров и свойств объектов.
Функции, присвоенной add, аннотации не нужны: типы её параметров берутся из Operation. Это называется контекстной типизацией, и это главная причина давать типам функций имена.
Синтаксис типа функции
Форма такая: (parameters) => ReturnType. Каждому параметру нужны имя и тип. Имена нужны для читаемости и подсказок редактора; подходит любая функция с совместимыми типами параметров, как бы она их ни называла.
type Predicate = (value: number) => boolean;
type Formatter = (value: number, digits?: number) => string; // optional parameter
type Logger = (...parts: string[]) => void; // rest parameter
type Factory = () => { id: number }; // no parameters
Имя не является необязательным. (string) => void объявляет параметр с именем string без типа, и строгий режим отвергает это с TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?. Пишите (value: string) => void.
Типизация стрелочных функций
Стрелочную функцию можно типизировать двумя способами. Аннотировать саму функцию или аннотировать переменную типом функции, чтобы параметры вывелись.
Форма 1 обычный выбор для самостоятельной функции. Форма 3 окупается, когда у нескольких функций одна сигнатура (обработчики, компараторы, конвертеры): сигнатура пишется один раз, и её изменение проверяется везде.
Типы колбэков
Параметр, тип которого является типом функции, это колбэк. Функция вызывающего кода проверяется по нему, а её параметры из него выводятся.
Два правила делают колбэки удобными:
- Меньше параметров можно. Функцию с одним параметром можно передать туда, где предлагаются два. JavaScript игнорирует лишние аргументы, поэтому TypeScript это разрешает. Больше параметров, чем предлагает тип, это ошибка (
Target signature provides too few arguments.). - Возврат
voidпринимает что угодно. Колбэк с типом(...) => voidможет вернуть значение; вызывающий код обещает его не использовать. Поэтомуlist.forEach((x) => other.push(x))компилируется, хотяpushвозвращает число.
Типы параметров всё равно должны быть совместимы. Передача (x: string) => ... туда, где ожидается (a: number, b: number) => number, падает с Types of parameters 'x' and 'a' are incompatible.
Сигнатуры вызова
(n: number) => string это сокращение для объектного типа с сигнатурой вызова: { (n: number): string }. Длинная форма нужна, когда у функции есть ещё и свойства.
TypeScript позволяет добавлять свойства функции, объявленной через const, в той же области видимости и отслеживает их в типе функции. Сигнатуру вызова может содержать и интерфейс: interface Counter { (): number; count: number }.
Сигнатура конструирования описывает то, что вызывается через new. Перед ней добавляется new: new (name: string) => User или { new (name: string): User } в объектной форме. Так типизируют параметр, который получает класс.
Синтаксис метода и синтаксис свойства
В объектном типе функциональный член можно записать как метод, handle(value: string): void, или как свойство, хранящее функцию, handle: (value: string) => void. Выглядят они взаимозаменяемыми, но в режиме strict проверяются по-разному:
Компилятор сообщает:
index.ts(8,27): error TS2322: Type '(value: string) => void' is not assignable to type '(value: string | number) => void'.
Types of parameters 'value' and 'value' are incompatible.
Type 'string | number' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
Ошибка справедлива: onlyStrings не умеет обрабатывать число. Форма метода пропускает ту же ошибку (параметры методов по историческим причинам проверяются «бивариантно»), и a.handle(42) упал бы во время выполнения с TypeError: value.toUpperCase is not a function. Удалите строку с b, чтобы увидеть это падение. Когда вы пишете свои объектные типы, форма свойства ловит больше.
Избегайте типа Function
Function это встроенный тип, которому соответствует любое значение-функция. Почти никогда это не то, что нужно: TypeScript не знает ни параметров, ни возвращаемого типа, поэтому любой вызов принимается и возвращает any.
Замените Function настоящей сигнатурой. Если вы действительно принимаете любую функцию (обобщённый debounce, обёртку для логирования), (...args: never[]) => unknown принимает любую функцию и при этом оставляет результат unknown, а не any. О функциях, типы которых зависят от входных данных, читайте на странице дженерики: обобщённый тип функции выглядит как <T>(value: T) => T.
Часто задаваемые вопросы
Как описать тип функции в TypeScript?
Используйте стрелочный синтаксис: (a: number, b: number) => number. Дайте ему имя через псевдоним типа, type Compare = (a: number, b: number) => number;, и используйте это имя для переменных, параметров и свойств. Имена параметров входят в синтаксис, но служат только документацией: функция с другими именами параметров тоже подходит.
Как типизировать параметр-колбэк в TypeScript?
Укажите тип функции как тип параметра: function onEach(items: string[], cb: (item: string, index: number) => void) { ... }. Вызывающий код может передать функцию с меньшим числом параметров, а параметры самого колбэка выводятся из этого типа, так что onEach(list, (item) => ...) не требует аннотаций.
Почему не стоит использовать тип Function в TypeScript?
Function принимает любую функцию, а её вызов не проверяется: разрешены любые аргументы, а результат имеет тип any. Напишите настоящую сигнатуру, например () => void или (value: string) => number, или (...args: never[]) => unknown, если вы действительно принимаете любую функцию.
Чем тип функции отличается от сигнатуры вызова?
Они описывают одно и то же. (n: number) => string это сокращение для объектного типа { (n: number): string }. Форма с сигнатурой вызова нужна, когда у функции есть ещё и свойства, например { (n: number): string; label: string }.
Как типизировать стрелочную функцию в TypeScript?
Либо аннотируйте её параметры и возвращаемый тип прямо на месте, const half = (n: number): number => n / 2;, либо дайте переменной тип функции, и параметры выведутся: const half: (n: number) => number = (n) => n / 2;.