30
loading...
This website collects cookies to deliver better user experience
// Create a new proxy state to detect mutations
const state = proxy({ count: 0 });
// You can mutate it
++state.count;
// Create a snapshot
const snap1 = snapshot(state); // ---> { count: 1 }
// Mutate it again
state.count *= 10;
// Create a snapshot again
const snap2 = snapshot(state); // ---> { count: 10 }
// The previous snapshot is not changed
console.log(snap1); // ---> { count: 1 }
// You can subscribe to it
subscribe(state, () => {
console.log('State changed to', state);
});
// Then, mutate it again
state.text = 'hello'; // ---> "State changed to { count: 10, text: 'hello' }"
useSyncExternalStore
. It's designed to safely use an external store in React. Our proxy object in Valtio is exactly an external store.snapshot
function to create immutable state, it should be pretty straightforward.// Create a state
const stateFoo = proxy({ count: 0, text: 'hello' });
// Define subscribe function for stateFoo
const subscribeFoo = (callback) => subscribe(stateFoo, callback);
// Define snapshot function for stateFoo
const snapshotFoo = () => snapshot(stateFoo);
// Our hook to use stateFoo
const useStateFoo = () => useSyncExternalStore(
subscribeFoo,
snapshotFoo
);
useCallback
.stateFoo
, suppose we have a component that shows the text
value in stateFoo
.const TextComponent = () => {
const { text } = useStateFoo();
return <span>{text}</span>;
};
count
value in stateFoo
, like ++stateFoo.count
, this TextComponent
actually re-renders, but produces the same result because it doesn't use the count
value, and the text
value isn't changed. So, this is an extra re-render.const TextComponent = () => {
const { text } = useStateFoo(['text']);
return <span>{text}</span>;
};
text
value is used in TextComponent
.// An array to store accessed properties
const accessedProperties = [];
// Wrap stateFoo with Proxy
const obj = new Proxy(stateFoo, {
get: (target, property) => {
accessedProperties.push(property);
return target[property];
},
});
// Use it
console.log(obj.text);
// We know what are accessed.
console.log(accessedProperties); // ---> ['text']
// More complex state
const obj = { nested: { count: 0, text: 'hello' }, others: [] };
// Use a nested property
console.log(obj.nested.count);
// As a result, `nested.count` is detected as used.
// `nested.text` and `others` are known to be unused.
useSnapshot
. It returns an immutable snapshot, but it's wrapped with proxies for render optimization.import { proxy, useSnapshot } from 'valtio';
const state = proxy({ nested: { count: 0, text: 'hello' }, others: [] });
const TextComponent = () => {
const snap = useSnapshot(state);
return <span>{snap.nested.text}</span>;
};
text
value is changed. Even if count
or others
changes, it won't re-render.useSnapshot
is a little bit tricky, and we don't dive in deep. Basically, it's just a combination of useSyncExternalStore
and proxy-compare
.useSnapshot
. You basically define a state object with proxy
, use it with useSnapshot
and you can mutate the state object as you like. The library takes care of everything else.Map
. Another example is that proxies can't detect a use of Object.keys
.