TypeScript函数类型校验中unknown[]与any[]兼容表现差异问题
function foo(a: number, b: number) { return a + b; } type Foo1 = typeof foo extends (...args: unknown[]) => unknown ? true : false; // false type Foo2 = typeof foo extends (...args: any[]) => unknown ? true : false; // true
问题描述
在TypeScript的类型扩展兼容校验逻辑中,为什么上述代码判断函数类型兼容性时,使用any[]作为剩余参数类型时校验结果为true,使用unknown[]作为剩余参数类型时校验结果为false?
实际测试中元组类型、ReturnType相关的类型校验都可正常兼容unknown[],仅剩余参数场景存在该差异,参考代码如下:
type Foo = ['bar', 'baz'] extends unknown[] ? true : false; // true
原因解析
这个差异是TypeScript函数参数逆变规则、any/unknown的类型特性、剩余参数匹配逻辑三者共同作用的结果:
- 基础规则前提
- 函数参数位置的兼容性检查遵循逆变规则:要让
源函数类型 extends 目标函数类型成立,两个类型同一位置的参数必须满足目标参数类型 extends 源参数类型,和返回值位置的协变规则(源返回值类型 extends 目标返回值类型)判定方向相反。 unknown是TS的顶级类型,所有其他类型都可以赋值给unknown,但unknown仅能赋值给unknown和any。any是TS的类型系统逃生舱,不遵守常规协变/逆变规则,任何类型都可赋值给any,any也可赋值给任何类型,遇到any时TS会跳过严格兼容性检查。- 普通数组、元组、返回值位置的类型检查遵循协变规则,和参数位置的逆变逻辑完全不同。
- 函数参数位置的兼容性检查遵循逆变规则:要让
- 剩余参数的匹配逻辑
当目标函数(extends关键字后的函数类型)的参数是...args: T[]形式时,TS会将其识别为「支持传入0到无限个类型为T的参数」,每个参数位置的类型都为T,需要对源函数声明的所有参数位置逐一做逆变校验。 - 两个类型判定的具体逻辑
- 对于
Foo2:剩余参数类型为any[],TS跳过严格的参数逆变检查,直接判定类型兼容,因此结果为true。 - 对于
Foo1:目标函数每个参数位置的类型都是unknown,源函数foo的参数类型为number,按照逆变规则需要满足unknown extends number,该条件显然不成立,因此结果为false。
- 对于
- 元组场景结果差异的原因
['bar', 'baz'] extends unknown[]属于普通数组/元组的协变检查,元组内的字面量类型'bar'、'baz'都是unknown的子类型,因此校验直接通过,和函数参数位置的逆变检查不属于同一套判定逻辑,结果没有可比性。
内容的提问来源于stack exchange,提问作者Alexey Perestoronin
相关产品推荐
相关产品推荐

