TypeScript函数extends unknown函数签名返回false原因及解法
为什么Bar的计算结果为false
核心原因是TypeScript函数类型检查遵循参数逆变规则:
判断函数A extends 函数B是否成立时,参数位置的类型兼容是反向校验的:A的每个参数类型必须比B对应位置的参数类型范围更宽(是B参数类型的父类型),否则兼容不成立。Bar里用来匹配的目标类型是(...args: unknown[]) => unknown,这个类型要求函数必须能接收任意类型、任意数量的传入参数;但被检查的(str: string) => void只能接收string类型参数,传其他类型就会报错,不满足参数兼容要求,自然返回false。
而Foo返回true是因为any是TS的特殊类型,会直接绕过严格的类型兼容性校验,所以所有函数都能匹配(...args: any[]) => any,但这种写法确实不符合TS的最佳实践,会引入类型安全漏洞。
无any的最优实现方案
利用类型兼容规则调整匹配用的函数类型即可,不需要用any:
type IsFunction<T> = T extends (...args: never[]) => unknown ? true : false
效果验证
type Foo = IsFunction<(str: string) => void> // true,和原Foo表现完全一致 type TestNoArg = IsFunction<() => number> // true,无参函数也能正确识别 type TestObj = IsFunction<{ id: number }> // false,非函数类型正确返回false type TestPrimitive = IsFunction<boolean> // false
实现原理
- 参数位置用
never[]:never是TS里所有类型的子类型,按照参数逆变规则,任何合法函数的参数类型范围都比never[]宽,天然满足参数兼容要求 - 返回值位置用
unknown:unknown是TS的顶层类型,所有类型的返回值都满足返回值协变的兼容要求
整个实现没有用到any,完全走严格类型检查,没有安全隐患。如果需要额外区分类构造函数(带new调用签名的类型),可以在这个基础上再加一层构造签名的判断,普通函数检测场景下这个写法完全够用。
内容的提问来源于stack exchange,提问作者TrevTheDev
相关产品推荐
相关产品推荐

