TypeScript: функция высшего порядка вычисляет возвращаемый тип функции обратного вызова без его явного указания

Рейтинг: 1Ответов: 1Опубликовано: 28.04.2023

Как typescript'у удаётся вычислить возвращаемый тип функции wrapper, если я не передаю никаких параметров-типов в дженерик? TS Playground

function wrapper<T>(cb: () => T) {
    return cb();
}

const result = wrapper(() => 'anything'); // string
const result1 = wrapper(() => 1); // number

Как называется этот приём и где можно об этом почитать? Есть ли более правильный способ написания подобной функции(без ненужного дженерика)?

Ответы

▲ 2Принят

Типовые параметры ("дженерики") функций могут быть выведены из переданных аргументов, что здесь и происходит. ТС сопоставляет тип аргумента функции с типом аргумента в выражении её вызова и пытается вывести тип для <T>. В хендбуке есть соответствующий раздел для ознакомления. К сожалению, у TS отсутствует официальная спецификация и нормальная документация, поэтому изучить механизм вывода типов более детально можно только в пул реквестах, ишью и исходниках на гитхабе.

"Более правильного" способа написать эту функцию не существует, без <T> здесь не обойтись. Вас не должно смущать что он явно не указывается при вызове. Типовой параметр позволяет подставлять типы при вызове, а не при определении, что даёт возможность выстраивать зависимости между типами в сигнатуре функции. Соответственно, если такие зависимости вам нужны, вы должны написать дженерик.

В вашем случае возвращаемый тип функции зависит от типа её аргумента.

Пример:

declare function f(arg: unknown): typeof arg
declare function g<T>(arg: T): typeof arg

const a = f(1)
const b = g(1)

Поскольку тип аргумента функции f задан при определении, typeof arg всегда будет unknown. Но для функции g он будет вычисляться каждый раз при вызове, и typeof arg будет возвращать выведенный тип. Дженерик здесь убрать нельзя.

Если же заменить тип возвращаемого значения в этих функция, например, на void, то зависимостей от типа аргумента не останется. В таком случае нам не будет важно в какой именно момент определился тип аргумента, поэтому дженерик будет излишним.

declare function f(arg: unknown): void
declare function g<T>(arg: T): void

f(1)
g(1)