如何让TypeScript中foobar(bar)因bar参数缺失b属性报错?
为什么当前代码不报错?
默认情况下,TypeScript的函数参数兼容性采用双向协变规则:只要两个函数的参数类型存在属性重叠(比如都包含a),就会被判定为兼容,哪怕其中一个参数缺少另一个的属性(比如bar的参数没有b)。若未开启strictFunctionTypes编译选项,TS不会对这种不严格的兼容进行报错。
即使开启strictFunctionTypes(启用参数逆变规则),bar的参数类型{a: string}与foo的参数类型{a: any, b: any}互相不属于子类型关系,此时TS会直接判定bar无法赋值给typeof foo,自然触发报错。
让foobar(bar)报错的两种方法
方法1:开启strictFunctionTypes编译选项
在tsconfig.json中添加或修改配置:
{ "compilerOptions": { "strictFunctionTypes": true } }
开启后,TS会严格遵循函数参数的逆变规则,bar因参数缺少b属性,会被直接标记为类型不兼容。
方法2:显式约束函数参数类型
如果不想依赖编译选项,可以通过类型工具显式约束foobar的入参必须能接受foo的完整参数类型:
const foo = (arg: {a: any, b: any}) => 1; // 提取foo的参数类型 type RequiredParams = Parameters<typeof foo>[0]; // 强制要求传入的函数必须能处理RequiredParams类型的参数 const foobar = <T extends (arg: RequiredParams) => ReturnType<typeof foo>>(arg: T) => 1; const bar = (arg: {a: string}) => 1; foobar(bar); // 报错:类型不兼容
这种方式通过Parameters工具类型提取foo的参数结构,约束foobar的入参必须支持该结构,从而让缺少b的bar被判定为无效。
关于逆变的解释
函数参数的逆变规则是:若类型A是类型B的子类型,那么(B) => void是(A) => void的子类型。简单来说,能接受更宽泛参数(超类型)的函数,可以赋值给需要接受更具体参数(子类型)的函数类型。比如,能处理Animal的函数,必然能处理Dog(Dog是Animal的子类型),因此前者可以赋值给后者的类型。
在你的例子中,foo的参数是{a: any, b: any},bar的参数是{a: string},两者互相不属于子类型,所以在严格逆变模式下,bar无法兼容typeof foo,会触发报错。
内容的提问来源于stack exchange,提问作者TrevTheDev

