TypeScript中两个泛型Interface用extends比较为何返回false?
背景
我一直在尝试深入理解extends的工作机制,之前看到的资料里把A extends B的含义描述为:
"A是B的超集"
或者
"A是B的可能更具体的版本"
还有的说法是extends等价于"可代入",这些内容都帮我更好地理解了相关机制。
问题场景
我写了一些示例代码用工具类型验证逻辑,但是遇到了一个预期返回true实际返回false的场景:
type DoesExtend<A, B> = A extends B ? true : false; interface MyInterface<A, B = void> { doSomething(input: A): B extends void ? A : A | B; } class MyClass1 implements MyInterface<string> { doSomething(input: string): string { return input; } } class MyClass2 implements MyInterface<string, number> { doSomething(input: string): string | number { return input; } }
以下对比结果都符合我的预期:
DoesExtend<MyClass1, MyClass2> //true DoesExtend<MyClass2, MyClass1> //false DoesExtend<MyClass1, MyInterface<string>> // true DoesExtend<MyInterface<string>, MyClass1> // true DoesExtend<MyClass1, MyInterface<string, number>> // true DoesExtend<MyInterface<string, number>, MyClass1> // false DoesExtend<MyClass2, MyInterface<string>> // false DoesExtend<MyInterface<string>, MyClass2> // true DoesExtend<MyClass2, MyInterface<string, number>> // true DoesExtend<MyInterface<string, number>, MyClass2> // true
但是当我对比两个MyInterface的实例时,有一个结果不符合预期:
DoesExtend<MyInterface<string>, MyInterface<string, number>> // false - 我预期为true DoesExtend<MyInterface<string, number>, MyInterface<string>> // false DoesExtend<MyInterface<string, number>, MyInterface<string, number>> // true DoesExtend<MyInterface<string, number>, MyInterface<string, number | Function>> // true
疑问
已知以下表达式结果都为true:
DoesExtend<MyClass1, MyClass2> DoesExtend<MyClass1, MyInterface<string, number>> DoesExtend<MyInterface<string>, MyClass2>
为什么看起来逻辑等价的DoesExtend<MyInterface<string>, MyInterface<string, number>>返回false?
回答
这个问题的核心是TypeScript对同一泛型接口不同实例的兼容性检查规则和普通结构兼容性检查的差异:
- 首先明确:
MyInterface<string>本质是MyInterface<string, void>,因为你给第二个泛型参数B设置了默认值void。 - 对于你定义的
MyInterface,第二个泛型参数B出现在doSomething方法的返回值位置,属于协变位置,因此TypeScript会自动推导该泛型参数为协变:也就是说,要满足MyInterface<A, B1> extends MyInterface<A, B2>,必须先满足B1 extends B2。 - 你要判断的是
MyInterface<string, void> extends MyInterface<string, number>,对应需要void extends number,这个判断的结果天然是false,因此整个表达式返回false。
至于你提到的其他看起来等价的表达式返回true,是因为它们都是不同类型之间的结构对比,不会触发泛型参数的方差检查:
- 比如对比
MyInterface<string>和MyClass2时,TypeScript会直接展开两个类型的成员做检查:MyInterface<string>的doSomething返回值是string,MyClass2的doSomething返回值是string | number,函数返回值协变,string是string | number的子类型,因此兼容,返回true。 - 同理
MyClass1 extends MyInterface<string, number>也是结构对比的结果,和泛型参数方差无关。
你可以用这个规则验证你给出的其他结果:比如DoesExtend<MyInterface<string, number>, MyInterface<string, number | Function>>返回true,就是因为number extends number | Function为true,符合协变参数的兼容要求。
内容的提问来源于stack exchange,提问作者james
相关产品推荐
相关产品推荐

