#typescript #types #typescript-typings #typing
#typescript #типы #typescript-типизации #ввод
Вопрос:
У меня есть функция identityOrCall
, которая либо вызывает заданную ей функцию, либо возвращает значение. value
является универсальным типом.
function identityOrCall <T>(value: T): T {
if (typeof value === 'function')
return value()
else
return value
}
identityOrCall(() => 'x') // -> 'x'
Строка identityOrCall(() => 'x')
, похоже, проходит проверку типа компилятора.
Почему? Разве это не должно выдавать ошибку?
Если identityOrCall
передается функция, я бы ожидал, что общий тип T
будет установлен на Function
, и это identityOrCall
должно возвращать Function
тип.
Вместо этого я могу передать Function
тип в качестве аргумента и identityOrCall
вернуть string
тип.
Почему это так? Мне это кажется непоследовательным, но я, должно быть, что-то упускаю.
Комментарии:
1.
identityOrCall(() => 'x')
не вернет ошибку (с чего бы это?) в этом случаеT
будет() => string
, который является допустимым типом2. @apokryfos Мое недоразумение заключается в том, что я ожидаю
identityOrCall
вернуть тот же тип, что и его аргумент. ЕслиFunction
в качестве аргумента указан тип, я бы ожидал, что он вернетFunction
тип.
Ответ №1:
Проблема в том, что, поскольку тип возвращаемого значения функции нигде не указан, TypeScript принимает его тип равным any
. any
не является типобезопасным; его можно назначить чему угодно. Здесь TS извлекается T
из any
возвращаемого значения (поскольку any
его можно сузить до T).
Лучше избегать типов any
, когда это возможно. Мне нравится использовать TSLint no-unsafe-any
, который запрещает подобные вещи.
По аналогичным причинам следующее не выдает ошибку TypeScript (и запрещено с использованием приведенного выше правила компоновки), хотя это явно небезопасно:
declare const fn: () => any;
function identityOrCall<T>(value: T): T {
return fn();
}
const result = identityOrCall(() => 'x') // -> 'x'
// result is typed as: () => 'x'. Huh?
Комментарии:
1. Я не уверен, что это правильное объяснение. Я использую
"noImplicitAny"
в своемtsconfig.json
. Это также, по-видимому, не приводит к ошибкам компиляции:identityOrCall(function (): string { return 'x' })
2. На самом деле, я думаю, что теперь понимаю. Таким
T
образом, здесь предполагается «самый слабый» тип, который естьany
, в отличие от немедленного принятия типа первогоT
введенного аргумента.
Ответ №2:
Если цель состоит в том, чтобы метод вел себя по-разному в зависимости от параметров, я думаю, что самый простой способ — указать перегрузки
function identityOrCall<T>(value: () => T): T;
function identityOrCall<T>(value: T): T;
function identityOrCall <T>(value: () => T|T): T {
if (typeof value === 'function')
return value()
else
return value
}
const result = identityOrCall(() => 'x') // first overload
const result2 = identityOrCall('x') // second overload
result.split(' '); // No error
result2.split(' '); // No error
result(); // error not callable
При вызове метода TS разрешает сигнатуру, просматривая перегрузки, пока не найдет ту, которая соответствует (здесь имеет значение порядок определения перегрузок).