How Proxies Help State Usage Tracking

proxy-compare is an Internal Library Used in Valtio

Hi,

Valtio consists of two parts: the vanilla part and the React part. The vanilla part handles write operations, while the React part handles read operations.

For more information, check out these two blog posts I wrote previously:

Today, we’ll take a closer look at the React part. It uses an internal library called proxy-compare for state usage tracking.

What is State Usage Tracking?

State usage tracking is the process of detecting which parts of the state are accessed. Let’s look at a simple example:

const state = { a: 1, b: 2 };

console.log(state.a);

// Now, `.a` is used, but `.b` isn't.

In this case, the state object has two properties, but only .a is accessed. .b is completely unused.

We won’t go into full detail in this post, but tracking which parts of the state are actually used is important for reactivity. If we know .b is never used, we can skip triggering reactivity when .b changes.

Tracking State Usage with Proxies

How can we track state usage? One way is to use Proxies. Here’s a simple example:

const used = new Set();

const createProxy = (obj) => new Proxy(obj, {
  get(target, prop) {
    used.add(prop);
    return target[prop];
  },
});

With this, the earlier example works like this:

const state = createProxy({ a: 1, b: 2 });

console.log(state.a);

console.log([...used]); // ---> ['a']

We successfully track the usage of .a, and we know .b is unused. I hope you get the idea.

Alternative: Tracking with Object.defineProperty

The same kind of tracking can also be achieved with object properties instead of Proxies.

const used = new Set();

const createTrackingObject = (target) => {
  const obj = {};
  Object.keys(target).forEach((prop) => {
    Object.defineProperty(obj, prop, {
      get() {
        used.add(prop);
        return target[prop];
      },
      set(val) {
        target[prop] = val;
      },
    });
  });
  return obj;
};

This works with the same example:

const state = createTrackingObject({ a: 1, b: 2 });

console.log(state.a);

console.log([...used]); // ---> ['a']

A Question for Readers

There is one big limitation with createTrackingObject. I will leave it as a question for you to think about.

Happy coding.

Reply

or to participate.