如何为类中的动态对象/Record设置正确的TypeScript类型?
实现可动态追加属性的TypeScript服务类
要让Services实例支持动态添加属性且保留完整类型提示,核心是利用泛型类结合类型扩展机制,让TypeScript实时跟踪已添加的属性类型。以下是具体实现方案:
1. 基础泛型类实现
class Services<T extends Record<string, unknown> = {}> { // items类型与泛型T绑定,彻底避免any items: T = {} as T; // add方法通过泛型K、V扩展当前类的泛型类型 add<K extends string, V>(key: K, value: V): Services<T & Record<K, V>> { (this.items as T & Record<K, V>)[key] = value; // 将属性直接挂载到实例,支持services.test这种直接访问方式 (this as any)[key] = value; return this as Services<T & Record<K, V>>; } // 索引签名允许实例直接访问动态添加的属性 [key: string]: unknown; } // 示例服务类 class TestService { greet() { console.log("Hello from TestService"); } }
2. 使用示例
// 初始实例类型为Services<{}> const services = new Services(); // 添加test属性后,实例类型自动扩展为Services<{ test: TestService }> const updatedServices = services.add('test', new TestService()); // 两种访问方式都有完整类型提示 updatedServices.items.test.greet(); // ✅ 类型提示正常 updatedServices.test.greet(); // ✅ 实例直接访问也有提示 // 继续添加新属性,类型会进一步扩展 const finalServices = updatedServices.add('logger', { log: (msg: string) => console.log(msg) }); finalServices.logger.log("Hi"); // ✅ 新属性同样有类型提示
3. 无需重新赋值的进阶版本
如果希望services变量本身自动更新类型(不用每次赋值给新变量),可以用TypeScript的断言函数实现:
class Services<T extends Record<string, unknown> = {}> { items: T = {} as T; // 断言函数告诉TS:调用add后,当前实例类型已扩展 add<K extends string, V>(key: K, value: V): asserts this is Services<T & Record<K, V>> { (this.items as T & Record<K, V>)[key] = value; (this as any)[key] = value; } [key: string]: unknown; } // 使用断言函数的版本 const services = new Services(); services.add('test', new TestService()); services.test.greet(); // ✅ 无需重新赋值,直接有类型提示
关键细节说明
- 泛型跟踪类型:
Services<T>中的泛型T始终记录已添加的所有属性类型,每次add都会返回扩展了新属性的类型实例。 - 避免any类型:
items的类型绑定到T,而非宽泛的object或Record<string, any>,确保所有值的类型都被TS正确推断。 - 实例直接访问:通过索引签名和直接挂载属性,支持
services.test这种直观的访问方式,而非只能通过items层级访问。
内容的提问来源于stack exchange,提问作者Andrey Popov
相关产品推荐
相关产品推荐

