restate / v1.0.0
组件拥有的状态
为每个文档、对话框或请求提供专属的 store:随组件一起创建,在组件卸载后释放。
useRestateScope
工厂函数声明并返回 store。restate 会在多次渲染之间保留结果,并在组件卸载后销毁 scope。不需要编写关闭函数或清理用的 effect。
两个 Document 元素拥有各自独立的状态。要和子组件共享一个文档,可以通过 props 或你自己的 context 传递返回的 Hook。
import { restate, useRestateScope } from '@kkwaenggwari/restate';
function Document() {
const { useDraft, useSelection } = useRestateScope((scope) => ({
useDraft: restate('', { scope }),
useSelection: restate(0, { scope }),
}));
const text = useDraft();
const selection = useSelection();
return (
<section>
<input value={text} onChange={(event) => useDraft.set(event.target.value)} />
<button onClick={() => useSelection.set((n) => n + 1)}>Selection: {selection}</button>
</section>
);
}工厂函数的规则
React 可能多次调用工厂函数并丢弃结果,所以请保持它同步且纯净。
- 在工厂函数中声明 store;在组件的顶层调用返回的 Hook。
- 初始 props 只读取一次,就像 useState 的初始化函数。
- 需要为另一个文档提供全新状态时,给组件加上 key。
- 不传工厂函数时,useRestateScope() 会返回一个稳定的 scope,供你之后在 effect 或事件处理函数中创建 store。
用 props 提供初始值
把 props 作为初始值,用 React 的 key 为另一个文档重新开始。
function Editor({ document }) {
// The initial value is captured once, like a useState initializer.
const useTitle = useRestateScope((scope) => restate(document.title, { scope }));
const title = useTitle();
return <input value={title} onChange={(event) => useTitle.set(event.target.value)} />;
}
// A different document gets fresh state.
<Editor key={document.id} document={document} />;服务端渲染
在服务端路由中加载数据并作为 prop 传入。初始值同时也是服务端快照。在 Next.js App Router 中,这个 Hook 应放在 Client Component 里,Client Component 同样会在服务端渲染。
'use client';
import { restate, useRestateScope } from '@kkwaenggwari/restate';
export default function Profile({ user }) {
const useProfile = useRestateScope((scope) => restate(user, { scope }));
const profile = useProfile();
return <p>{profile.name}</p>;
}等待文档的已保存数据
scope 中的持久化 store 在组件于客户端提交后才开始恢复,不包含在全局的 restateManager.ready 中。请在加载边界中等待该 scope 自己的 manager.ready。
import { useEffect, useState } from 'react';
import { persistedRestate, useRestateScope } from '@kkwaenggwari/restate';
function Document({ documentId }) {
const { manager, useDraft } = useRestateScope((scope) => ({
manager: scope.manager,
useDraft: persistedRestate('', { scope, namespace: 'documents', key: documentId }),
}));
const [state, setState] = useState('loading');
useEffect(() => {
let active = true;
manager.ready.then(
() => active && setState('ready'),
() => active && setState('error'),
);
return () => {
active = false;
};
}, [manager]);
if (state === 'error') return <p>Could not load this document.</p>;
if (state === 'loading') return <p>Loading document…</p>;
return <Editor useDraft={useDraft} />;
}动手试试
打开和关闭文档,看看它们的状态如何重新开始。
正在准备示例…
React 之外的所有者
当服务或服务端请求拥有状态时,自己创建 scope,用完后调用 destroy()。销毁 scope 绝不会删除已保存的记录。
import { createRestateScope, persistedRestate } from '@kkwaenggwari/restate';
export function createDocument(documentId) {
const scope = createRestateScope();
const useDraft = persistedRestate('', {
scope,
namespace: 'documents',
key: documentId,
});
return {
useDraft,
manager: scope.manager,
save: () => scope.manager.saveAll(),
destroy: () => scope.destroy(),
};
}