restate / v1.0.0
何时保存由你决定
把 store 保存到 IndexedDB 或 MMKV,启动时加载一次,显式或自动保存,并显示未保存的修改。
声明持久化 store
把 restate 换成 persistedRestate 并传入 key。组件继续使用同一个 Hook 和 set。
创建后会自动恢复已保存的值。编辑只改变内存;在你保存或开启 autoSave 之前不会写入任何内容。
import { persistedRestate } from '@kkwaenggwari/restate';
export const useDraft = persistedRestate('', {
key: 'article-draft',
validate: (value) => typeof value === 'string',
});启动时只等待一次
在客户端入口导入应用及其 store,然后在渲染前等待 restateManager.ready。之后组件就能直接读取已保存的值。
存储不可用(例如服务端渲染或浏览器禁用了存储)不算失败:这些 store 会在内存中工作。只有当记录无法读取、迁移或校验时,ready 才会被拒绝。
import { createRoot } from 'react-dom/client';
import { restateManager } from '@kkwaenggwari/restate';
import App from './App';
async function start() {
const root = createRoot(document.getElementById('root'));
try {
await restateManager.ready;
root.render(<App />);
} catch (error) {
root.render(<p>Could not load saved data.</p>);
console.error(error);
}
}
start();保存、撤销修改与重置
save 写入当前值,restore 回到已保存的值,resetAll 同时清空内存和存储。它们都返回 Promise。
await useDraft.save(); // write the current value
await useDraft.restore(); // return to the saved value
await useDraft.resetAndClearStorage(); // initial value in memory, record removed显示未保存的修改和进度
持久化状态变化时 useStatus 会重新渲染。当内存与上次加载、保存或恢复的值不同时,dirty 为 true。operation 表示正在执行的存储操作。
传入 selector,只在某一个字段变化时重新渲染。
function SaveButton() {
const { dirty, operation, error } = useDraft.useStatus();
const saving = operation === 'save';
return (
<>
<button disabled={!dirty || saving} onClick={() => useDraft.save().catch(() => {})}>
{saving ? 'Saving…' : 'Save'}
</button>
{error != null && <p>Could not save your draft.</p>}
</>
);
}动手试试
输入内容,调高存储延迟后保存。重新播放示例,可以看到已保存的草稿被恢复。
正在准备示例…
自动保存
对于始终需要保存的设置和草稿,开启一次即可。autoSave: true 在每次变化后保存;{ delayMs } 会等到变化停顿。
连续的变化会合并为一次写入,改回已保存的值则不写入。失败会显示在 status().error 中。组件拥有的 scope 在卸载时会写入待处理的修改。
export const useTheme = persistedRestate('light', { key: 'theme', autoSave: true });
export const useDraft = persistedRestate('', {
key: 'article-draft',
autoSave: { delayMs: 500 },
});页面关闭之前
在 React 之外也能读取同样的状态。离开前如有未保存的修改可以提醒用户,或在页面隐藏时保存全部内容。
window.addEventListener('beforeunload', (event) => {
if (useDraft.status().dirty) event.preventDefault();
});
window.addEventListener('pagehide', () => {
restateManager.saveAll();
});处理存储错误
存储错误会让该操作的 Promise 被拒绝。每个 restate 错误都有 code,持久化 store 的错误还带有记录的 key。
try {
await useDraft.save();
} catch (error) {
if (error.code === 'STORAGE_UNAVAILABLE') showNotice('Saving is off in this browser.');
else if (error.code === 'STORAGE_VALUE_TOO_LARGE') showNotice('This draft is too large.');
else showNotice(`Could not save "${error.key ?? 'draft'}".`);
}