TypeScript动态创建类并根据首个参数约束第二个参数类型
动态类实例化的类型约束问题
需求说明
需要实现一个方法,根据传入的枚举值动态实例化对应类,第二个参数必须严格匹配该类构造函数所需的Model类型。例如传入MenuElement.Products时,第二个参数只能传入ProductModel对象。
现有代码
enum MenuElement { Categories = 'Categories', Products = 'Products', } type ProductModel = { name:string; age:number id:string; } type CategoryModel = { name:string; favorite:boolean id:string; } class Product implements ProductModel{ name ='' age= 0; id = ''; constructor(model:ProductModel) { } } class Category implements CategoryModel{ name='' favorite=true id='' constructor(model:CategoryModel) { } } const MenuElements = { [MenuElement.Products]:Product, [MenuElement.Categories]:Category } type KeysInEnum = keyof typeof MenuElement type ModelTypeForNewElement<Z extends keyof typeof MenuElements, Type extends typeof MenuElements[Z]> = { [Prop in keyof Type]: Type[Prop]; } type GEN = keyof typeof MenuElement; class DynamicMenuElement { static instance: DynamicMenuElement; public static get() { if (!DynamicMenuElement.instance) { DynamicMenuElement.instance = new DynamicMenuElement(); } return DynamicMenuElement.instance; } public createMenuElement<T extends GEN>(type: T, model: ModelTypeForNewElement<T,typeof MenuElements[T]>) { const ElementType = MenuElements[type] if (!ElementType) { throw new Error(`Cannot find the class type of ${type}`); } return new ElementType(model); } } const creator = DynamicMenuElement.get(); creator.createMenuElement(MenuElement.Categories,/*代码提示需要Category Model(符合预期) */) creator.createMenuElement(MenuElement.Products,/*代码提示需要Product Model(符合预期) */)
当前问题
代码中ElementType的类型被自动推断为:
const ElementType: new (model: ProductModel & CategoryModel) => Product | Category
但预期的类型应该是:
const ElementType: new (model: ProductModel | CategoryModel) => Product | Category
参数被合并为所有Model的交叉类型,而非符合逻辑的联合类型,导致类型校验不符合预期。
解决方案
通过显式建立枚举值、类、Model的一一映射关系,避免TypeScript自动合并类型时产生错误推断:
enum MenuElement { Categories = 'Categories', Products = 'Products', } type ProductModel = { name:string; age:number id:string; } type CategoryModel = { name:string; favorite:boolean id:string; } class Product implements ProductModel{ name ='' age= 0; id = ''; constructor(model:ProductModel) {} } class Category implements CategoryModel{ name='' favorite=true id='' constructor(model:CategoryModel) {} } // 显式定义枚举与类、Model的映射关系 type MenuElementMap = { [MenuElement.Categories]: { cls: typeof Category; model: CategoryModel }; [MenuElement.Products]: { cls: typeof Product; model: ProductModel }; }; const MenuElements: MenuElementMap = { [MenuElement.Products]: { cls: Product, model: {} as ProductModel }, [MenuElement.Categories]: { cls: Category, model: {} as CategoryModel } }; class DynamicMenuElement { static instance: DynamicMenuElement; public static get() { if (!DynamicMenuElement.instance) { DynamicMenuElement.instance = new DynamicMenuElement(); } return DynamicMenuElement.instance; } // 利用泛型约束实现精准的类型匹配 public createMenuElement<T extends keyof MenuElementMap>( type: T, model: MenuElementMap[T]['model'] ) { const elementEntry = MenuElements[type]; if (!elementEntry) { throw new Error(`Cannot find the class type of ${type}`); } return new elementEntry.cls(model); } } const creator = DynamicMenuElement.get(); // 自动提示并校验CategoryModel参数 creator.createMenuElement(MenuElement.Categories, { name: 'Food', favorite: true, id: '1' }); // 自动提示并校验ProductModel参数 creator.createMenuElement(MenuElement.Products, { name: 'Apple', age: 1, id: '2' });
关键改动说明
- 新增
MenuElementMap类型,明确建立枚举值与对应类、Model的一一绑定,避免TypeScript自动合并类型时生成交叉类型。 - 调整
MenuElements对象的类型为MenuElementMap,确保每个枚举值对应的条目类型准确。 - 在
createMenuElement方法中,通过MenuElementMap[T]['model']直接获取当前枚举值对应的Model类型,实现参数的精准约束,同时实例化时的类型推断也会正确匹配到具体类。
内容的提问来源于stack exchange,提问作者VarJohn
相关产品推荐
相关产品推荐

