TypeScript函数参数泛型及接口继承赋值兼容性问题咨询
TypeScript 函数类型继承与泛型赋值报错的核心原因
普通值类型的兼容性遵循协变规则:子类型的值类型可以窄于父类型,只要子类型的属性值是父类型属性值的子集即可兼容,这也是第一个接口继承示例可以正常运行的原因:Constraint的value是Basic的value的子集,符合协变要求。
而函数参数的兼容性遵循逆变规则:如果子类型要覆盖父类型的函数属性,子类型函数的参数类型必须是父类型函数参数类型的父集,而非子集。
逆变规则的设计逻辑
这是TypeScript为了避免运行时报错做的类型保护:
假设第二个示例的接口继承可以通过,那么就可以写出如下存在运行风险的代码:
const constraint: WithFunctionsConstraint = { value: (t: "foo") => { console.log(t.length) } } const basic: WithFunctionsBasic = constraint // 按照父接口定义,此处可以合法传"bar",但实际运行时constraint的value只能处理"foo",会出现预期外的行为 basic.value("bar")
子接口中定义的value只能处理"foo"类型的入参,但是父接口允许传入"foo" | "bar" | "baz",如果允许继承,就会出现父接口合法调用触发子类型逻辑报错的问题,因此TypeScript会直接阻止这种继承。
泛型赋值报错的原因
定义的泛型接口WithFunctionsParametric<T>中,泛型参数T被用在了函数的参数位置,因此整个泛型类型的兼容性也遵循逆变规则:
WithFunctionsParametric<Basic>的value可以接收"foo" | "bar" | "baz"三种入参,完全可以覆盖WithFunctionsParametric<Constraint>要求的"foo"入参,因此WithFunctionsParametric<Basic>是WithFunctionsParametric<Constraint>的子类型,可以把variableBasic赋值给variableConstraint。- 反过来
WithFunctionsParametric<Constraint>的value只能接收"foo",无法满足父类型WithFunctionsParametric<Basic>的入参要求,因此无法赋值,这和接口继承报错的底层逻辑完全一致。
额外补充
如果业务场景确实需要实现泛型的协变,需要将泛型参数放在函数返回值位置而非参数位置,TypeScript 4.7及以上版本也支持通过out关键字显式声明协变、in关键字显式声明逆变,但协变标记out只能作用于返回值位置的泛型参数。
内容的提问来源于stack exchange,提问作者Quadear
相关产品推荐
相关产品推荐

