状态快照 #
什么是状态快照? #
状态快照(Snapshot)是 Recoil 在某一时刻的状态冻结副本。通过快照,我们可以:
- 检查当前状态
- 实现时间旅行调试
- 状态回滚和恢复
- 状态对比和差异检测
获取快照 #
useRecoilSnapshot #
jsx
import { useRecoilSnapshot } from 'recoil';
function DebugObserver() {
const snapshot = useRecoilSnapshot();
useEffect(() => {
console.log('Current snapshot:', snapshot);
for (const node of snapshot.getNodes_UNSTABLE()) {
const loadable = snapshot.getLoadable(node);
console.log(node.key, loadable.contents);
}
}, [snapshot]);
return null;
}
useRecoilCallback #
jsx
import { useRecoilCallback } from 'recoil';
function MyComponent() {
const getSnapshot = useRecoilCallback(({ snapshot }) => () => {
return snapshot;
});
const handleClick = () => {
const snapshot = getSnapshot();
console.log('Snapshot:', snapshot);
};
return <button onClick={handleClick}>Get Snapshot</button>;
}
Snapshot API #
getLoadable #
同步获取状态值:
jsx
const loadable = snapshot.getLoadable(state);
if (loadable.state === 'hasValue') {
console.log('Value:', loadable.contents);
}
getPromise #
异步获取状态值:
jsx
const value = await snapshot.getPromise(state);
console.log('Value:', value);
getNodes #
获取所有状态节点:
jsx
const nodes = snapshot.getNodes_UNSTABLE();
for (const node of nodes) {
console.log(node.key);
}
getNodes_UNSTABLE #
带过滤条件获取节点:
jsx
const modifiedNodes = snapshot.getNodes_UNSTABLE({ isModified: true });
for (const node of modifiedNodes) {
console.log('Modified:', node.key);
}
状态调试 #
调试组件 #
jsx
import { useRecoilSnapshot } from 'recoil';
function DebugPanel() {
const snapshot = useRecoilSnapshot();
const nodes = Array.from(snapshot.getNodes_UNSTABLE());
return (
<div className="debug-panel">
<h3>State Debug</h3>
<ul>
{nodes.map(node => {
const loadable = snapshot.getLoadable(node);
return (
<li key={node.key}>
<strong>{node.key}:</strong>
<pre>{JSON.stringify(loadable.contents, null, 2)}</pre>
</li>
);
})}
</ul>
</div>
);
}
变化追踪 #
jsx
function StateChangeLogger() {
const snapshot = useRecoilSnapshot();
useEffect(() => {
const modifiedNodes = snapshot.getNodes_UNSTABLE({ isModified: true });
for (const node of modifiedNodes) {
const loadable = snapshot.getLoadable(node);
console.log(`[${node.key}] changed:`, loadable.contents);
}
}, [snapshot]);
return null;
}
function App() {
return (
<RecoilRoot>
<StateChangeLogger />
<YourApp />
</RecoilRoot>
);
}
时间旅行 #
实现历史记录 #
jsx
import { atom, useRecoilSnapshot, useRecoilCallback } from 'recoil';
const historyState = atom({
key: 'history',
default: [],
dangerouslyAllowMutability: true,
});
const historyIndexState = atom({
key: 'historyIndex',
default: -1,
});
function useHistory() {
const snapshot = useRecoilSnapshot();
const saveSnapshot = useRecoilCallback(({ set }) => () => {
const state = {};
for (const node of snapshot.getNodes_UNSTABLE()) {
const loadable = snapshot.getLoadable(node);
if (loadable.state === 'hasValue') {
state[node.key] = loadable.contents;
}
}
set(historyState, prev => [...prev, state]);
set(historyIndexState, prev => prev + 1);
});
const undo = useRecoilCallback(({ set, snapshot }) => async () => {
const history = await snapshot.getPromise(historyState);
const index = await snapshot.getPromise(historyIndexState);
if (index > 0) {
const prevState = history[index - 1];
for (const [key, value] of Object.entries(prevState)) {
set({ key }, value);
}
set(historyIndexState, index - 1);
}
});
const redo = useRecoilCallback(({ set, snapshot }) => async () => {
const history = await snapshot.getPromise(historyState);
const index = await snapshot.getPromise(historyIndexState);
if (index < history.length - 1) {
const nextState = history[index + 1];
for (const [key, value] of Object.entries(nextState)) {
set({ key }, value);
}
set(historyIndexState, index + 1);
}
});
return { saveSnapshot, undo, redo };
}
gotoSnapshot #
恢复到指定快照:
jsx
function TimeTravel() {
const [snapshots, setSnapshots] = useState([]);
const snapshot = useRecoilSnapshot();
const saveSnapshot = useRecoilCallback(({ snapshot }) => () => {
setSnapshots(prev => [...prev, snapshot]);
});
const gotoSnapshot = useRecoilCallback(({ gotoSnapshot }) => (snapshot) => {
gotoSnapshot(snapshot);
});
return (
<div>
<button onClick={saveSnapshot}>Save Snapshot</button>
<div>
{snapshots.map((snap, index) => (
<button key={index} onClick={() => gotoSnapshot(snap)}>
Restore #{index + 1}
</button>
))}
</div>
</div>
);
}
状态检查 #
验证状态完整性 #
jsx
function validateSnapshot(snapshot) {
const nodes = snapshot.getNodes_UNSTABLE();
const errors = [];
for (const node of nodes) {
const loadable = snapshot.getLoadable(node);
if (loadable.state === 'hasError') {
errors.push({
key: node.key,
error: loadable.contents,
});
}
}
return errors;
}
function StateValidator() {
const snapshot = useRecoilSnapshot();
useEffect(() => {
const errors = validateSnapshot(snapshot);
if (errors.length > 0) {
console.error('State validation errors:', errors);
}
}, [snapshot]);
return null;
}
状态差异对比 #
jsx
function diffSnapshots(snapshot1, snapshot2) {
const diff = {};
const nodes = snapshot1.getNodes_UNSTABLE();
for (const node of nodes) {
const value1 = snapshot1.getLoadable(node).contents;
const value2 = snapshot2.getLoadable(node).contents;
if (JSON.stringify(value1) !== JSON.stringify(value2)) {
diff[node.key] = {
from: value1,
to: value2,
};
}
}
return diff;
}
实战示例:完整的调试工具 #
jsx
import { useState } from 'react';
import { useRecoilSnapshot, useRecoilCallback } from 'recoil';
function RecoilDebugger() {
const [isOpen, setIsOpen] = useState(false);
const [history, setHistory] = useState([]);
const [currentIndex, setCurrentIndex] = useState(-1);
const snapshot = useRecoilSnapshot();
const captureSnapshot = () => {
const state = {};
for (const node of snapshot.getNodes_UNSTABLE()) {
const loadable = snapshot.getLoadable(node);
if (loadable.state === 'hasValue') {
state[node.key] = {
value: loadable.contents,
timestamp: Date.now(),
};
}
}
setHistory(prev => [...prev.slice(0, currentIndex + 1), state]);
setCurrentIndex(prev => prev + 1);
};
const restoreSnapshot = useRecoilCallback(({ set }) => (state) => {
for (const [key, data] of Object.entries(state)) {
set({ key }, data.value);
}
});
const handleUndo = () => {
if (currentIndex > 0) {
const prevState = history[currentIndex - 1];
restoreSnapshot(prevState);
setCurrentIndex(prev => prev - 1);
}
};
const handleRedo = () => {
if (currentIndex < history.length - 1) {
const nextState = history[currentIndex + 1];
restoreSnapshot(nextState);
setCurrentIndex(prev => prev + 1);
}
};
if (!isOpen) {
return (
<button
style={{ position: 'fixed', bottom: 20, right: 20 }}
onClick={() => setIsOpen(true)}
>
Debug
</button>
);
}
const currentSnapshot = history[currentIndex];
const nodes = currentSnapshot ? Object.entries(currentSnapshot) : [];
return (
<div
style={{
position: 'fixed',
bottom: 20,
right: 20,
width: 400,
maxHeight: 500,
background: '#1e1e1e',
color: '#fff',
borderRadius: 8,
overflow: 'hidden',
}}
>
<div
style={{
padding: 10,
background: '#333',
display: 'flex',
justifyContent: 'space-between',
}}
>
<span>Recoil Debugger</span>
<button onClick={() => setIsOpen(false)}>×</button>
</div>
<div style={{ padding: 10 }}>
<div style={{ marginBottom: 10 }}>
<button onClick={captureSnapshot}>Capture</button>
<button onClick={handleUndo} disabled={currentIndex <= 0}>
Undo
</button>
<button onClick={handleRedo} disabled={currentIndex >= history.length - 1}>
Redo
</button>
</div>
<div style={{ maxHeight: 300, overflow: 'auto' }}>
{nodes.map(([key, data]) => (
<div key={key} style={{ marginBottom: 10 }}>
<strong style={{ color: '#4ec9b0' }}>{key}</strong>
<pre style={{ margin: 0, fontSize: 12 }}>
{JSON.stringify(data.value, null, 2)}
</pre>
</div>
))}
</div>
</div>
</div>
);
}
function App() {
return (
<RecoilRoot>
<YourApp />
<RecoilDebugger />
</RecoilRoot>
);
}
总结 #
状态快照的核心要点:
| 功能 | API |
|---|---|
| 获取快照 | useRecoilSnapshot |
| 读取状态 | snapshot.getLoadable |
| 异步读取 | snapshot.getPromise |
| 获取节点 | snapshot.getNodes_UNSTABLE |
| 恢复快照 | gotoSnapshot |
下一步,让我们学习 性能优化,了解 Recoil 的性能优化技巧。
最后更新:2026-03-28