Menu

Типы функций в TypeScript: стрелочный синтаксис и колбэки

Как записать тип функции в TypeScript: стрелочный синтаксис (a: number) => string, псевдонимы типов для функций, типизация стрелочных функций и колбэков, сигнатуры вызова и конструирования и почему тип Function слишком широкий.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Тип функции описывает её параметры и возвращаемое значение и записывается через стрелку: (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;.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ