TypeScript类使用Proxy支持任意属性且保留原有属性访问的解决方案咨询
TypeScript 代理类支持任意属性访问的最优解决方案
核心问题原因
TypeScript 默认只会按照类的显式声明推导实例类型,无法感知构造函数返回的Proxy对动态属性的支持能力,因此访问未声明的属性时会抛出类型错误。
在类内部直接添加索引签名的方案需要为所有原有属性加//@ts-ignore,会破坏类内部的类型检查,完全不推荐。
方案1:构造函数返回交叉类型(推荐,无侵入)
直接在类的构造函数上声明返回类型为原类类型 + 索引签名的交叉类型,既保留原有属性的类型提示和检查,又支持任意动态属性访问,不需要修改类内部代码,也不需要加任何忽略注释。
完整代码示例:
const handler = { get: (target: any, key: string) => { return key; }, set: (target: any, key: string, value: any) => { console.log(target, key, value); return true; } }; class ProxTest { private storage: {[key: string]: any} = {}; public a: number = 1; // 核心:声明构造函数返回交叉类型 constructor(): ProxTest & Record<string, any> { return new Proxy(this, handler); } } const c = new ProxTest(); c.a = 2; // 正常识别为number类型,有类型检查 c.val = "b"; // 不会报类型错误
如果需要约束动态属性的类型,只需要把Record<string, any>改成对应的类型即可,比如要求所有动态属性都是字符串,就改成Record<string, string>。
方案2:实例化时类型断言(适合临时使用场景)
如果不想修改类的定义,只需要在实例化时做一次类型断言即可,适合只需要少数实例支持动态属性的场景:
// 类定义保持原有代码不变 class ProxTest { private storage: {[key: string]: any} = {}; public a: number = 1; constructor() { return new Proxy(this, handler); } } // 实例化时做交叉类型断言 const c = new ProxTest() as ProxTest & Record<string, any>; c.val = "b"; // 正常使用
方案3:通用代理类工厂(适合批量场景)
如果有多个类需要做代理,可以封装通用工厂函数,复用逻辑:
// 定义代理后的实例类型 type Proxied<T> = T & Record<string, any>; // 通用工厂函数:传入原始类和proxy handler,返回代理后的类 function createProxiedClass<T extends new (...args: any[]) => any>( Cls: T, handler: ProxyHandler<InstanceType<T>> ): new (...args: ConstructorParameters<T>) => Proxied<InstanceType<T>> { return new Proxy(Cls, { construct(target, args) { const instance = new target(...args); return new Proxy(instance, handler) as Proxied<InstanceType<T>>; } }) as any; } // 使用示例 const ProxTest = createProxiedClass(class { private storage: {[key: string]: any} = {}; public a: number = 1; }, { get: (target, key: string) => key, set: (target, key: string, value) => { console.log(target, key, value); return true; } }); const c = new ProxTest(); c.val = "b";
内容的提问来源于stack exchange,提问作者valepu
相关产品推荐
相关产品推荐

