TypeScript泛型类类型守卫:如何正确返回对应类型?
TypeScript泛型类类型守卫失效问题解决
问题场景
想要实现一个泛型类,根据构造函数传入的类型参数返回对应类型的结果,但类型守卫未按预期生效,触发类型不匹配错误。
示例代码
type Type = 'foo' | 'bar'; interface Res { 'foo': {foo: number}; 'bar': {bar: string}; } class MyClass<T extends Type> { constructor(readonly type: T) {} public run(): Res[T] { if (is(this, 'foo')) { return { foo: 123 }; } return { bar: 'xyz' }; } } function is<T extends Type>(instance: MyClass<Type>, type: T): instance is MyClass<T> { return instance.type === type; }
报错信息
类型“{ foo: number; }”不能赋值给类型“Res[T]”。
类型“{ foo: number; }”不能赋值给类型“{ foo: number; } & { bar: string; }”。
类型“{ foo: number; }”缺少属性“bar”,但类型“{ bar: string; }”需要该属性。
问题原因
自定义的is类型守卫试图将MyClass<Type>缩小为MyClass<T>,但类本身的泛型参数T是实例级别的类型变量。TypeScript无法在守卫分支中,将类的泛型T与守卫的具体类型(如'foo')关联起来,因此仍会将Res[T]视为Res['foo']和Res['bar']的交集类型,导致返回值类型不匹配。
解决方案
方案1:直接守卫type属性的类型
修改类型守卫,直接针对实例的type属性进行类型缩小,让TypeScript能明确关联泛型T和返回类型:
type Type = 'foo' | 'bar'; interface Res { 'foo': {foo: number}; 'bar': {bar: string}; } class MyClass<T extends Type> { constructor(readonly type: T) {} public run(): Res[T] { if (isType(this, 'foo')) { return { foo: 123 }; // 此时TypeScript推断T为'foo',返回类型匹配Res['foo'] } return { bar: 'xyz' }; // 同理推断T为'bar' } } // 守卫type属性的类型 function isType<T extends Type>(instance: { type: Type }, type: T): instance is { type: T } { return instance.type === type; }
方案2:简化逻辑,直接判断type属性
如果不需要复用类型守卫,可直接在run方法中判断this.type,TypeScript能自动缩小泛型范围:
type Type = 'foo' | 'bar'; interface Res { 'foo': {foo: number}; 'bar': {bar: string}; } class MyClass<T extends Type> { constructor(readonly type: T) {} public run(): Res[T] { if (this.type === 'foo') { return { foo: 123 } as Res['foo']; } return { bar: 'xyz' } as Res['bar']; } }
方案3:调整类型守卫的泛型约束
通过重载类型守卫,让TypeScript正确识别实例的泛型类型:
type Type = 'foo' | 'bar'; interface Res { 'foo': {foo: number}; 'bar': {bar: string}; } class MyClass<T extends Type> { constructor(readonly type: T) {} public run(): Res[T] { if (is(this, 'foo')) { return { foo: 123 }; } return { bar: 'xyz' }; } } // 重载类型守卫,支持任意泛型的MyClass实例 function is<T extends Type>(instance: MyClass<any>, type: T): instance is MyClass<T>; function is<T extends Type>(instance: MyClass<Type>, type: T): instance is MyClass<T> { return instance.type === type; }
内容的提问来源于stack exchange,提问作者danikaze
相关产品推荐
相关产品推荐

