在 Class 上使用 Proxy,同时保持对现有属性和 class 类型的访问

Use Proxy on Class while maintaining access to existing properties and class type

为了学习 Proxy 的工作原理,我想尝试制作一个可以处理任何 属性 名称的 Proxied class,即使是 [=23= 中不存在的名称] ],同时仍然保持对旧属性的访问。我假设当 class 被 Proxied 时,Typescript 会让你本地执行此操作(因为这毕竟是 Proxy 用例之一),但我遗憾地发现情况并非如此。当我尝试做这样的事情时:

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;

    constructor() {
        return new Proxy(this, handler);
    }
}

const c = new ProxTest();
c.val = "b"; //Gives error

我在尝试访问 c.val 时遇到错误。我找到了一个“解决方案”,即使用“索引签名”并将 //@ts-ignore 添加到所有其他属性。

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 {
    //@ts-ignore
    private storage: {[key: string]: any} = {};
    //@ts-ignore
    public a: number;
    [key: string]: any;

    constructor() {
        return new Proxy(this, handler);
    }
}

const c = new ProxTest();
c.val = "b";

这是迄今为止我找到的唯一解决方案,老实说我不太喜欢它。还有其他方法吗?

需要说明的是,此代码只是一般问题的一个示例(无法在代理对象上使用任意命名的属性)并且它与我实际使用的某些代码的形状或形式无关.但是因为这似乎是在打字稿中使用代理时应该能够做的事情,所以我想找到一个通用的解决方案

因此,根据评论,我误解了 Proxy 的工作方式,我认为它的工作方式类似于 PHP 魔术方法(也就是说,get/set 陷阱仅在尝试访问未知时触发属性),而是适用于所有属性,甚至是对象中已经存在的属性。
我假设打字稿会自动检测代理 class 并让您像代理之前一样使用 class 的正常值,而在尝试访问未知属性时它不会给出错误,这是可能是为什么我不能说清楚我在问什么。
默认情况下,当使用 Proxy 时,typescript 会将类型更改为“any”,这当然允许您在对象上使用任何类型的 属性 而不会出现错误,代价是失去对已经存在的直接访问权限属性(这实际上符合 javascript 因为当你 Proxy a class 并声明 get/set 一切都通过这些方法时)

所以,我真正想要实现的是有一个代理 class,在那里我仍然可以访问已经存在的属性,并且打字稿可以正确地推断出已经存在的属性的类型,同时仍然没有尝试访问不存在的错误时出错。我想到了这个解决方案:

interface ProxTestStorage extends ProxTestConstructor {
    [key: string]: any;
}

interface ProxTestConstructor {
    new(): ProxTestImpl & ProxTestStorage;
}

class ProxTestImpl {
    protected storage: {[key: string]: any} = {};
    public a: number = 3;
}

const handler = {
    get: (target: any, key: string) => {
        console.log(target, key);
        return Reflect.get(key in target ? target : target.storage, key);
    },

    set: (target: any, key: string, value: any) => {
        console.log(target, key);
        return Reflect.set(key in target ? target : target.storage, key, value);
    }
};

const ProxTest = ProxTestImpl as any as ProxTestImpl & ProxTestStorage;
const c = new Proxy<typeof ProxTest>(new ProxTest(), handler);
c.val = "b"; //No error, type is any
c.a = 2; //No error, type is number

console.log(c); // ...{a: 2, storage: {val: "b"}}...

当然需要正确实现 get 和 set 才能让您访问现有属性(使用“in”的缺点是 属性 需要初始化才能看到,但因为这是“只是“一个理解代理的练习,我接受它)并且它们必须与你的类型的逻辑一致。通过像这样声明类型,我必须在使用 ProxTest 作为类型时使用“typeof ProxTest”(与正常的 classes 不同),但我想这是一个小问题,它超出了我的问题范围。

感谢@jcalz 的耐心等待:)