为何TypeScript中用不同类特化的泛型Resolver实例可赋值给不同类特化的变量?
为什么TypeScript允许将
Resolver<Mod2>赋值给Resolver<NoModule>? 这个问题的核心在于TypeScript的结构类型系统,以及你的泛型类和模块类型的结构兼容性。
1. 结构类型系统的本质
TypeScript判断类型兼容性时,不依赖类或接口的名称(名义类型),而是看它们的公共结构是否匹配。只要两个类型的公共成员(属性、方法)的签名互相兼容,TypeScript就会认为它们可以互相赋值。
在你的代码中:
Resolver<Mod2>和Resolver<NoModule>的实例结构完全一致:都有modules只读属性,以及add、get、all方法。Mod2和NoModule的结构也匹配:两者都有init()方法(签名完全相同),以及resolver只读属性(Resolver<Mod1>和Resolver<NoModule>的结构又再次兼容,形成递归的结构匹配)。
TypeScript不会因为Resolver的泛型参数是不同的类就拒绝赋值,因为这些类的公共结构没有冲突。
2. 泛型类的兼容性细节
对于泛型类,TypeScript会检查其泛型参数是否出现在协变/逆变位置,但在你的Resolver实现中:
modules是只读的Record<string, Modules>,属于协变位置(只产出值,不消费值)。add、get方法的泛型参数是方法级别的,不是类泛型参数的直接消费位置,所以TypeScript对这些方法的兼容性检查会放宽——只要方法的签名在结构上能匹配,就会认为兼容。
这就导致Resolver<Mod2>和Resolver<NoModule>被判定为结构兼容。
3. 如何让TypeScript区分这些类型
如果你想让TypeScript严格区分不同模块特化的Resolver,需要给你的模块类型添加名义类型标记,让它们的结构产生差异。
修改IModule接口,添加一个唯一的只读属性作为标记:
interface IModule<Deps extends IModule<any> = any> { readonly resolver: Resolver<Deps> init(): void // 添加名义类型标识符,每个实现类的这个属性值唯一 readonly moduleId: unique symbol }
然后在每个模块类中实现这个属性:
class NoModule implements IModule<NoModule> { readonly resolver: Resolver<NoModule> init() {} readonly moduleId = Symbol('NoModule') } class Mod1 implements IModule<NoModule> { readonly resolver: Resolver<NoModule> init() {} readonly moduleId = Symbol('Mod1') } class Mod2 implements IModule<Mod1> { readonly resolver: Resolver<Mod1> init() {} readonly moduleId = Symbol('Mod2') }
现在,Mod2和NoModule的结构因为moduleId是不同的unique symbol而不再兼容,对应的Resolver<Mod2>也无法赋值给Resolver<NoModule>,TypeScript会正确抛出类型错误。
内容的提问来源于stack exchange,提问作者Yehor Popovych
相关产品推荐
相关产品推荐

