·3 分钟阅读
Valtio 原理
前言
下面所说的都是非嵌套对象的情况
分为两个部分,vanilla 和 react
Vanilla
核心:为可变状态添加不变性
检测突变
使用Proxy
的set
来检测突变,使用版本号来标记这个对象已经发生了变化
let version = 0;
const p = new Proxy(
{},
{
set(target, prop, value) {
++version;
target[prop] = value;
},
}
);
p.a = 10;
console.log(version); // ---> 1
++p.a;
console.log(version); // ---> 2
快照
当调用snapshot
的时候,根据版本号创建快照
let version = 0;
let lastVersion;
let lastSnapshot;
const p = new Proxy(
{},
{
set(target, prop, value) {
++version;
target[prop] = value;
},
}
);
const snapshot = () => {
if (lastVersion !== version) {
lastVersion = version;
lastSnapshot = { ...p };
}
return lastSnapshot;
};
p.a = 10;
console.log(snapshot()); // ---> { a: 10 }
p.b = 20;
console.log(snapshot()); // ---> { a: 10, b: 20 }
++p.a;
++p.b;
console.log(snapshot()); // ---> { a: 11, b: 21 }
事件订阅
let version = 0;
const listeners = new Set();
const p = new Proxy(
{},
{
set(target, prop, value) {
++version;
target[prop] = value;
listeners.forEach((listener) => listener());
},
}
);
const subscribe = (callback) => {
listeners.add(callback);
const unsubscribe = () => listeners.delete(callback);
return unsubscribe;
};
subscribe(() => {
console.log("mutated!");
});
p.a = 10; // shows "mutated!"
++p.a; // shows "mutated!"
p.b = 20; // shows "mutated!"
React
核心:useSyncExternalStore and proxy-compare
useSyncExternalStore
// 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);
Automatic render optimization
const TextComponent = () => {
const { text } = useStateFoo();
return <span>{text}</span>;
};
如果我们更改stateFoo
中的count
数值(例如++stateFoo.count
,则此TextComponent
实际上会重新渲染,但会产生相同的结果,因为它不使用count
数值,并且text
值不会更改。所以,这是一次额外的重新渲染。
例如,如果我们假设 hook 接受字符串列表,我们将能够告诉如下属性。
const TextComponent = () => {
const { text } = useStateFoo(["text"]);
return <span>{text}</span>;
};
那么如何自动化呢
proxy-compare
我们想知道的是,在前面的示例中, text
值在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']
Valtio 提供了一个基于 proxy-compare 的钩子来实现自动渲染优化。
useSnapshot
Valtio 提供的hook
称为useSnapshot
。它返回一个不可变的快照,但它包含有用于渲染优化的Proxy
。
使用大概是
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
值更改时重新呈现。即使count
或others
发生变化,它也不会重新渲染。
基本上,它只是useSyncExternalStore
和proxy-compare
的组合。
参考
- How valtio works — Valtio, makes proxy-state simple for React and Vanilla (pmnd.rs)
- How Valtio Proxy State Works (Vanilla Part) · Daishi Kato's blog (axlight.com)
- How Valtio Proxy State Works (React Part) · Daishi Kato's blog (axlight.com)
- valtio / proxy-compare 源码解析 | Magicdawn
- Valtio 源码解析 - 掘金 (juejin.cn)