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(),
};
}