如何在不重复定义属性的情况下消除TypeScript装饰器类型报错?
解决TypeScript中装饰器注入属性的类型报错问题
首先得提一句:你当前的装饰器写法存在运行时隐患——类装饰器应该返回构造函数而非直接返回实例,不然testClass会被替换成单一实例,无法通过new创建新对象。我们先修正装饰器,同时解决类型报错的问题,而且完全不需要重复定义observables里的属性。
方法一:泛型装饰器自动推导属性类型
这个方法最省心,不需要手动额外定义类型,TypeScript会自动从observables中提取属性类型并合并到类实例上:
import { extendObservable } from "mobx"; // 假设你用的是Mobx的extendObservable // 提取类实例中observables的类型 type ExtractObservables<T> = T extends { observables: infer O } ? O : never; // 泛型类装饰器,将observables的属性合并到类的实例类型中 const func = <T extends { new (...args: any[]): { observables: Record<string, any> } }>(Constructor: T) => { return class extends Constructor { constructor(...args: any[]) { super(...args); // 将observables中的属性注入到实例 extendObservable(this, this.observables); } } as T & { prototype: InstanceType<T> & ExtractObservables<InstanceType<T>> }; }; export default func;
然后你的类可以正常使用,TypeScript会自动识别name/fname/age这些属性:
@func class testClass { observables = { name: "", fname: "", age: 0, }; someMethod() { let a = this.name; // 不再报错! let b = this.fname; let c = this.age; } } // 也可以正常创建实例 const instance = new testClass(); console.log(instance.name); // 类型为string,完全符合预期
原理说明
- 泛型
T约束为拥有observables属性的类构造函数 ExtractObservables类型工具从类实例中提取observables的结构类型- 通过类型断言将装饰后的类构造函数的原型类型合并为「原类实例类型 + observables属性类型」,让TypeScript知道这些注入的属性存在
方法二:接口合并(适合固定结构的observables)
如果你的observables结构相对固定,也可以用接口合并的方式,只需要定义一次类型:
// 先定义observables的类型 type TestObservables = { name: string; fname: string; age: number; }; // 接口合并,让testClass自动实现这个接口 interface testClass extends TestObservables {} // 修正后的装饰器(同样要返回构造函数) const func = <T extends { new (...args: any[]): { observables: TestObservables } }>(Constructor: T) => { return class extends Constructor { constructor(...args: any[]) { super(...args); extendObservable(this, this.observables); } }; }; @func class testClass { observables: TestObservables = { name: "", fname: "", age: 0, }; someMethod() { let a = this.name; // 无报错 } }
这个方法需要手动定义一次TestObservables类型,但比重复写类属性要简洁,而且结构变更时只需要修改这一处类型定义。
为什么原来的写法会报错?
TypeScript的类型系统是静态的,它不知道你的装饰器会在运行时给实例注入observables里的属性。所以必须通过类型技巧告诉TypeScript:这些属性会被添加到类实例上,这样就不会报「属性不存在」的错误了。
内容的提问来源于stack exchange,提问作者challenger
相关产品推荐
相关产品推荐

