- Daishi Kato's Read the Code
- Posts
- A Challenge with Proxies for State Usage Tracking
A Challenge with Proxies for State Usage Tracking
One Limitation of Proxy-Based Solutions Like proxy-compare
Hi,
Today, we are diving into a more detailed topic. You may not have noticed, but state usage tracking has a major challenge. Let’s explore it. There won’t be much coding this time, but it is an important concept to understand.
The Problem
Consider the following case:
const state = { obj: { a: 1, b: 2 } };
console.log(state.obj.a); // ---> what is "used" is ".obj.a"
We use dot notation to track property access, meaning that in this case, the tracked usage is ".obj.a"
.
In proxy-compare
, we do not actually use dot notation. Instead, we represent state access as a tree using a WeakMap-based structure.
Because we know that ".obj.a"
is what we care about, we can skip re-executing code if only ".obj.b"
changes. This helps optimize reactivity. So far, so good.
But what happens in this slightly modified example?
const state = { obj: { a: 1, b: 2 } };
console.log(state.obj);
console.log(state.obj.a);
// ---> what is "used" now?
The Challenge
Looking at the code, we can clearly see that ".obj"
and ".obj.a"
are both used. However, from a Proxy’s perspective, it cannot differentiate between ".obj"
and ".obj.a"
.
The Proxy first intercepts access to obj
, then to a
. Technically, what is tracked as “used” is both ".obj"
and ".obj.a"
, even though we may not want it that way.
You might think the solution is to assume that both ".obj"
and ".obj.a"
are used, but that does not work for the first case where we only access ".obj.a"
.
If we were to assume that ".obj"
is always used, then console.log(state.obj.a)
would unnecessarily re-execute when ".obj.b"
changes. This breaks the optimization we had in place.
The Solution: Explicit Tracking
It is technically impossible to track console.log(state.obj); console.log(state.obj.a);
as we want using only runtime solutions. To work around this, proxy-compare
provides an escape hatch:
const state = { obj: { a: 1, b: 2 } };
console.log(trackMemo(state.obj));
console.log(state.obj.a);
The trackMemo
function explicitly marks an object as used. By doing this, we assume that ".obj"
is tracked, so changes to ".obj.b"
will now correctly trigger reactivity.
The name trackMemo
originally came from its use case with React.memo
, but it is actually unrelated to memo
. I think I should rename it in a future version.
Today’s post was a bit technical. Hopefully, I can write about something more basic next time.
Happy coding.
Reply