TypeScript自定义类型映射含静态方法的类时报错如何解决
需要实现类到字符串的映射,目标调用形式如下:map.add(ClassName, 'my string');
最初定义的自定义类型:type NoConstructor<T> = Pick<T, keyof T>;
映射实例声明方式:const map: Map<NoConstructor<typeof Device>, string> = new Map();
测试发现map.add(ClassName, 'my string');可正常执行,但map.get(instanceOfClassName.constructor)仅在目标类未定义静态方法时可正常通过类型校验;一旦类包含静态方法,TypeScript就会抛出如下类型错误:
Argument of type 'Function' is not assignable to parameter of type 'NoConstructor'.
Type 'Function' is missing the following properties from type 'NoConstructor': getInputs, getOutputs
export abstract class Device { public getStatus(): string { return 'foo'; } // 移除该静态方法 public static getInputs(): {[key: string]: number} { return {}; } // 同时移除该静态方法后,代码可正常运行 public static getOutputs(): {[key: string]: number} { return {}; } } class DeviceA extends Device { } type NoConstructor<T> = Pick<T, keyof T>; export default class DeviceUpdater { private readonly map: Map<NoConstructor<typeof Device>, string> = new Map(); public add(deviceType: NoConstructor<typeof Device>, updater: string) { this.map.set(deviceType, updater); } public update(device: Device): void { const result = this.map.get(device.constructor); if (result === undefined) { throw new Error(`Cannot update device of type '${device.constructor.name}': no updater found`); } console.log(result); } } const deviceUpdater = new DeviceUpdater(); deviceUpdater.add(DeviceA, 'something'); deviceUpdater.update(new DeviceA());
- TypeScript中类的静态属性、静态方法全部挂载在类的构造函数对象上,只要类定义了自定义静态成员,
typeof 类名得到的类型就会自动包含这些静态成员。 - 自定义的
NoConstructor<T> = Pick<T, keyof T>没有做任何类型过滤,Pick<T, keyof T>等价于原类型T,所以Map的key类型实际要求传入的构造函数必须包含getInputs、getOutputs两个静态方法。调用add方法时传入的是显式的子类(比如DeviceA),它继承了父类Device的所有静态成员,符合类型要求,因此不会报错。 - TypeScript中所有实例的
.constructor属性默认类型是基础Function类型,这个基础类型不包含Device类上定义的自定义静态方法,无法满足Map的key类型约束,因此会抛出类型不匹配错误。当删除类上的所有静态方法时,typeof Device的结构和基础Function类型兼容,此时就不会触发类型报错。
不需要定义无效的NoConstructor类型,直接明确Map的key类型为「可实例化出Device实例的构造函数类型」即可,修复后的代码如下:
export abstract class Device { public getStatus(): string { return 'foo'; } public static getInputs(): {[key: string]: number} { return {}; } public static getOutputs(): {[key: string]: number} { return {}; } } class DeviceA extends Device { } // 定义Device子类的构造函数类型,如果需要校验静态方法,可在交叉类型中补充静态成员的类型定义 type DeviceConstructor = (new (...args: any[]) => Device) & { getInputs: typeof Device.getInputs; getOutputs: typeof Device.getOutputs; }; export default class DeviceUpdater { private readonly map: Map<DeviceConstructor, string> = new Map(); public add(deviceType: DeviceConstructor, updater: string) { this.map.set(deviceType, updater); } public update(device: Device): void { // 将实例的constructor断言为DeviceConstructor类型,匹配Map的key约束 const ctor = device.constructor as DeviceConstructor; const result = this.map.get(ctor); if (result === undefined) { throw new Error(`Cannot update device of type '${device.constructor.name}': no updater found`); } console.log(result); } } const deviceUpdater = new DeviceUpdater(); deviceUpdater.add(DeviceA, 'something'); deviceUpdater.update(new DeviceA());
如果不需要强制校验静态方法的存在,可以去掉交叉类型部分,直接写type DeviceConstructor = new (...args: any[]) => Device;即可。
内容的提问来源于stack exchange,提问作者TiMESPLiNTER

