You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 01:57:12