TypeScript 函数在不期望的情况下通过了类型检查

TypeScript function passes type check when not expected to

我有一个函数 identityOrCall,它要么调用给它的函数,要么 returns 值。 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 应该 return a Function类型。

相反,我可以将 Function 类型作为参数传递,并让 identityOrCall return 成为 string 类型。

这是为什么?这对我来说似乎不一致,但我一定遗漏了一些东西。

问题在于,由于函数 return 类型在任何地方都没有注明,TypeScript 将其类型设为 anyany 不是 type-safe;它可以分配给任何东西。这里,TS 从 any return 值中提取 T(因为 any 可以缩小到 T)。

最好尽可能避免 any 类型。我喜欢使用 TSLint 的 no-unsafe-any 来禁止这种事情。

出于类似的原因,以下不会引发 TypeScript 错误(并且禁止使用上述 linting 规则),即使它显然不安全:

declare const fn: () => any;
function identityOrCall<T>(value: T): T {
    return fn();
}

const result = identityOrCall(() => 'x') // -> 'x'
// result is typed as: () => 'x'. Huh?

如果目标是让方法根据参数表现不同,我认为最简单的方法是指定 overloads

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 将通过重载来解析签名,直到找到一个匹配的(意味着定义重载的顺序在这里很重要)。

Playground link