TypeScript运行时为类添加动态字段并保证类型安全的实现方法
解决方案
可以通过TS泛型+映射类型+声明合并的组合实现需求,核心思路是收紧模块name属性的类型范围,再从传入的模块联合类型中自动生成容器的动态属性类型,具体实现如下:
核心改动点
- 改造
Module基类,将name属性约束为字面量类型,确保TS可以提取每个模块的固定名称作为类型键 - 定义映射工具类型,将模块联合类型转换为「模块名-模块实例」的键值对类型
- 利用TS同名interface和class的声明合并特性,让容器类自动继承动态属性的类型定义
- 调整容器
init方法的入参类型,保证传入模块和泛型约束一致
完整实现代码
// 改造基类,约束name为字面量类型 abstract class Module<TName extends string> { public abstract readonly name: TName; } class ThisModule extends Module<'thisModule'> { public readonly name = 'thisModule' as const; public log = () => { console.log('ThisModule called!'); }; } class ThatModule extends Module<'thatModule'> { public readonly name = 'thatModule' as const; public log = () => { console.log('ThatModule called!'); }; } // 映射工具类型:从模块联合类型生成键值对类型 type ModuleMap<T extends Module<string>> = { [K in T['name']]: Extract<T, { name: K }> } // 声明合并:容器实例同时包含原生方法和动态模块属性 interface Container<TModule extends Module<string>> extends ModuleMap<TModule> {} class Container<TModule extends Module<string>> { public init = (modules: TModule[]) => { for (const module of modules) { // 类型断言合法,运行时确实会挂载对应属性 (this as unknown as ModuleMap<TModule>)[module.name] = module; } }; } // 测试代码 const thisModule = new ThisModule(); const thatModule = new ThatModule(); // 传入模块联合类型作为泛型参数 const container = new Container<ThisModule | ThatModule>(); container.init([thisModule, thatModule]); // 无类型错误,可正确推导log方法类型 container.thisModule.log(); container.thatModule.log(); // 触发类型错误:Property 'anythingElse' does not exist on type 'Container<ThisModule | ThatModule>' container.anythingElse.log();
扩展说明
如果希望进一步简化调用,不需要显式传入泛型参数,可以额外封装工厂函数,让TS自动从init的入参推导模块类型,不需要手动写联合类型。
内容的提问来源于stack exchange,提问作者nerdlinger
相关产品推荐
相关产品推荐

