TypeScript+Angular:泛型函数中Type<T>赋值失败问题
我最近写泛型函数createMock_UserService的时候碰到个头疼的问题:这个函数要求传入的类型是Parent的子类,当我直接传Child类型调用时一切正常,但如果不传类型参数,想在函数内部默认把Child赋值给type参数时,就会弹出“Type 'Child' is not assignable to type 'T'”的错误。
先贴一下我的代码示例:
export class Parent { } export class Child extends Parent { } export function createMock_UserService<T extends Parent>(type?: Type<T>) { if(!type) { // 此处报错 -> Type 'Child' is not assignable to type 'T' type = Child; } // 使用type进行操作 injector.get(type); } // 该调用方式正常 createMock_UserService(Child); // 该调用方式报错 createMock_UserService();
这里用到的Type<T>来自Angular及TypeScript核心代码,定义如下:
export declare const Type: FunctionConstructor; interface FunctionConstructor { /** * 创建新函数。 * @param args 函数接受的参数列表。 */ new(...args: string[]): Function; (...args: string[]): Function; readonly prototype: Function; }
问题根源
其实问题出在泛型类型的自动推断上:当你调用createMock_UserService()不传任何参数时,TypeScript会把泛型T默认推断成Parent(因为T extends Parent且没有其他约束)。这时候type的类型是Type<T>也就是Type<Parent>,但你要赋值的Child是Type<Child>类型——虽然Child是Parent的子类,但Type<Child>和Type<Parent>在TypeScript的类型系统里并不兼容,所以就抛出了错误。
而直接传Child调用时,TypeScript会自动把T推断成Child,type的类型正好匹配,所以完全没问题。
解决方案
给你几个可行的解决办法,按需选择:
1. 给泛型指定默认类型
最优雅的方式是给泛型T设置默认类型为Child,这样当不传参数时,T就会被自动推断成Child,赋值自然就合法了:
export function createMock_UserService<T extends Parent = Child>(type?: Type<T>) { if(!type) { type = Child as Type<T>; // 加个类型断言让TypeScript彻底放心 } injector.get(type); }
2. 使用类型断言绕过检查
如果你不想修改泛型的默认类型,也可以用类型断言明确告诉TypeScript“我确定这个赋值是安全的”:
if(!type) { type = Child as unknown as Type<T>; }
不过这种方式要谨慎使用——如果后续有人把T指定为Parent的其他子类,这个默认赋值就可能带来类型风险,但在你的场景下,默认值是Child,所以是安全的。
3. 调整函数参数的类型约束
另一种思路是把参数类型改成Type<Parent>,然后在内部处理泛型适配,但这种方式会损失一部分类型安全性,不如前两种方法实用,这里就不展开细说了。
内容的提问来源于stack exchange,提问作者Arikael

