调用实例方法后,TypeScript能否扩展该实例的类型?
动态扩展属性的JS类的TypeScript类型定义解决方案
问题背景
我正在为一个JS库编写类型定义,现有如下Table类,它通过register方法动态给实例添加属性:
class Table { register(...fields) { fields.forEach(field => { Object.defineProperty(this, field, { get: () => { console.log(`Accessed ${field}`); return 1; }, }) }) } }
实际使用示例:
let table = new Table(); table.register("uuid", "name", "url"); table.name; // 取值为1并打印"Accessed name"
最初写的类型定义很简单:
class Table { register(...fields: string[]): void; }
但调用register后,TypeScript无法识别新增的属性,会报错:name不是Table的属性。
已知如果让register返回实例,可以通过泛型实现类型变更,但如果方法本身无返回值,怎么让TypeScript感知实例类型的变化?
解决方案:类型断言函数+泛型追踪
不需要修改原JS代码,仅通过类型定义就能实现需求,核心是利用TypeScript的类型断言函数结合泛型追踪已注册属性:
完整类型定义
class Table<T extends string = never> { // 若需要支持传入数组形式的属性列表,可改为:register<U extends string>(fields: U[]): asserts this is Table<T | U> register<U extends string>(...fields: U[]): asserts this is Table<T | U> { fields.forEach(field => { Object.defineProperty(this, field, { get: () => { console.log(`Accessed ${field}`); return 1; }, }) }) } // 为已注册属性指定类型(对应JS逻辑中的返回值类型) [key in T]: number; }
使用验证
let table = new Table(); table.register("uuid", "name", "url"); table.name; // TypeScript不再报错,自动推断类型为number table.uuid; // 新增属性均能被正确识别
原理说明
- 泛型追踪属性集合:泛型参数
T用来记录当前实例已注册的属性名,初始值设为never表示无额外属性。 - 类型断言函数:
asserts this is Table<T | U>是TypeScript的特殊语法,它告知编译器:调用该方法后,当前实例的类型会扩展为Table<T | U>,即新增了U中传入的所有属性。 - 索引签名约束类型:
[key in T]: number为所有已注册的属性统一指定类型,这里对应JS代码中返回的数值类型。
内容的提问来源于stack exchange,提问作者Avishay Matayev
相关产品推荐
相关产品推荐

