Hey everyone! I'd like to propose a way to create proxies for primitive. I wrote a small draft here: GitHub - hugoattal/tc39-proposal-primitive-proxy: Primitive proxy proposal
Here's the essence of it:
TC39 proposal: Reference proxy
Synopsis
The proposed reference proxy is aimed at providing a mechanism for creating reference variables with user-defined set and get functions. This allows developers to add custom functionality to primitive types without having to wrap them in objects.
Prerequisite
Maybe:
Motivation
The proposed reference proxy would simplify properties of reactive frameworks such as React or Vue. These frameworks rely heavily on the ability to create reactive properties that can update automatically in response to changes in the underlying data.
There are two solutions:
- React-like reactive properties use a getter to get the variable, and a function to set it.
- Vue-like reactive properties wrap the variable into a Proxy, where the value can be set and get using the
valueproperty.
A reference proxy would allow setting and getting a reactive variable seamlessly, here's an example:
// React
const [myReactVar, setMyReactVar] = useState("");
setMyReactVar("Hello world");
console.log(myReactVar);
// Vue
const myVueVar = ref("");
myVueVar.value = "Hello world";
console.log(myVueVar.value);
// Primitive proxy
let myReferenceVar = makeRef("");
myReferenceVar = "Hello world";
console.log(myReferenceVar );
Possible API
Here is a possible implementation inspired by the Proxy object, but without the prop argument of the handler get and set.
let myVar = new ReferenceProxy({ storedValue: "" }, {
set: (target, value) => {
target.storedValue = value + " world";
},
get: (target) => {
return target.storedValue + " !";
}
});
myVar = "Hello";
console.log(myVar); // "Hello world !"
Here, storedValue is just for example purpose. The first argument of ReferenceProxy is an object where you can store values that can be accessed with the target argument of set and get.
API details
The ReferenceProxy constructor takes two arguments: target and handler. target is an object to store values, and handler is an object containing the methods to intercept the operations performed on the target. The handler object can have get and set methods, which will be called when the target is accessed or modified.
ReferenceProxy<TTarget, TPrimitive>(
target: TTarget,
handler: {
set: (target: TTarget, value: TPrimitive) => void
get: (target: TTarget) => TPrimitive
}
);
Questions
Isn't it confusing not to be able to reassign a variable?
It's the same thing as standard Proxy. Setting a value to myObject.thing does not necessary mean that the thing property of myObject contains the set value, if the object is behind a Proxy.
Why using a let instead of a const?
For consistency, const should not be updatable. So a const Primitive Proxy should disable the set handler.
How to unproxify a reference proxy?
Simply use the valueOf function like this:
let a = new PrimitiveProxy(...);
let b = a.valueOf(); // b is a standard primitive
What happens when passing a Reference Proxy to a function?
When passed to a function, the Reference Proxy is copied just like an object.
let a = new ReferenceProxy({storedValue: ""}, {
set: (target, value) => {
target.storedValue = value;
},
get: (target) => {
return "primitive:" + target.storedValue;
}
});
a = "Hello";
console.log(a); // "primitive:Hello"
((value) => {
console.log(value); // "primitive:Hello"
value = "test";
console.log(value); // "primitive:test"
})(a);
console.log(a); // "primitive:test"
So, when is getter called?
The getter is called when the primitive value of the Reference Proxy is needed.
Here are some examples:
let a = new ReferenceProxy({storedValue: ""}, {
set: (target, value) => {
console.log("set");
target.storedValue = value;
},
get: (target) => {
console.log("get");
return target.storedValue;
}
});
a = 5 // set
console.log(a) // get
const b = a; // copy Primitive Proxy
const c = a + 5 // get
const d = 5 + a // get
const e = "Hello" + a // get
const f = (a === 5) // get
What do you guys think of this?