الحالة لـ React وReact Native · 1.0
شارك الحالة.خطّاف واحد.انتهى.
عرّف مخزنًا في سطر واحد واستخدمه في أي مكوّن، من دون Provider أو action أو reducer. لا يُعاد عرض المكوّن إلا عندما تتغيّر القيمة التي اختارها، ويحدث الحفظ حين تقرّر أنت.
npm install @kkwaenggwari/restateمباشر — مخزن restate حقيقي
useCart.set(cart => ({ ...cart, items: cart.items + 1, total: cart.total + 12 }))يتغيّر items وtotal: تُعرض ثلاث بطاقات، وتبقى بطاقة الهدية ثابتة.
01 — سهل
الميزة نفسها بمفاهيم أقل.
أربعة تطبيقات للمهمة نفسها جنبًا إلى جنب. الأرقام مأخوذة من الشيفرة المعروضة هنا وتُقاس أثناء بناء هذا الموقع.
المهمة عدّاد مشترك · اختيار حقل · حفظ مسودة · حالة لكل مستند
عدّاد مشترك
مكوّنان يقرآن الرقم نفسه ويحدّثانه.
Redux Toolkit ينظّم التحديثات في slice وaction تُرسَل إلى مخزن يُوفَّر عبر سياق React. أدوات DevTools ممتازة وتدفّق تحديث صارم ومتوقّع للفرق الكبيرة.
Zustand ينشئ خطّاف مخزن عبر create() ويغيّره عبر setState. صغير جدًا ومرن، مع middleware للحفظ وDevTools.
Jotai يركّب atom وatom مشتقة، اختياريًا داخل Provider. تبعيات دقيقة بين قطع حالة صغيرة كثيرة.
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useDispatch, useSelector } from 'react-redux';
const slice = createSlice({
name: 'count',
initialState: 0,
reducers: { increment: (count) => count + 1 },
});
const store = configureStore({
reducer: { count: slice.reducer },
});
function Counter() {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
return <button onClick={() => dispatch(slice.actions.increment())}>{count}</button>;
}
export default function App() {
return (
<Provider store={store}>
<Counter />
<Counter />
</Provider>
);
}import { create } from 'zustand';
const useCount = create(() => ({ count: 0 }));
const increment = () =>
useCount.setState((s) => ({
count: s.count + 1,
}));
function Counter() {
const count = useCount((s) => s.count);
return <button onClick={increment}>{count}</button>;
}
export default function App() {
return (
<>
<Counter />
<Counter />
</>
);
}import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return <button onClick={() => setCount((n) => n + 1)}>{count}</button>;
}
export default function App() {
return (
<>
<Counter />
<Counter />
</>
);
}restate تعريف واحد هو المخزن والخطّاف معًا.
import { restate } from '@kkwaenggwari/restate';
const useCount = restate(0);
function Counter() {
const count = useCount();
return <button onClick={() => useCount.set((n) => n + 1)}>{count}</button>;
}
export default function App() {
return (
<>
<Counter />
<Counter />
</>
);
}في هذه المهمة، Zustand وJotai أيضًا صغيران ومن دون Provider. تتّسع الفجوة حين يدخل الحفظ والحالة لكل مستند.
اختيار حقل
يجب ألا يُعاد عرض مكوّن العنوان عندما يتغيّر النص وحده.
Redux Toolkit ينظّم التحديثات في slice وaction تُرسَل إلى مخزن يُوفَّر عبر سياق React. أدوات DevTools ممتازة وتدفّق تحديث صارم ومتوقّع للفرق الكبيرة.
Zustand ينشئ خطّاف مخزن عبر create() ويغيّره عبر setState. صغير جدًا ومرن، مع middleware للحفظ وDevTools.
Jotai يركّب atom وatom مشتقة، اختياريًا داخل Provider. تبعيات دقيقة بين قطع حالة صغيرة كثيرة.
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useSelector } from 'react-redux';
const article = createSlice({
name: 'article',
initialState: { title: 'Hello', body: '' },
reducers: {
editBody: (i) => {
i.body += '!';
},
editTitle: (i) => {
i.title += '!';
},
},
});
const store = configureStore({ reducer: article.reducer });
function Title() {
const title = useSelector((i) => i.title);
return <h3>{title}</h3>;
}
export default function App() {
return (
<Provider store={store}>
<Title />
<button onClick={() => store.dispatch(article.actions.editBody())}>body + !</button>
<button onClick={() => store.dispatch(article.actions.editTitle())}>title + !</button>
</Provider>
);
}import { create } from 'zustand';
const useArticle = create(() => ({ title: 'Hello', body: '' }));
function Title() {
const title = useArticle((i) => i.title);
return <h3>{title}</h3>;
}
export default function App() {
return (
<>
<Title />
<button
onClick={() =>
useArticle.setState((i) => ({
body: i.body + '!',
}))
}
>
body + !
</button>
<button
onClick={() =>
useArticle.setState((i) => ({
title: i.title + '!',
}))
}
>
title + !
</button>
</>
);
}import { atom, useAtomValue, useSetAtom } from 'jotai';
const articleAtom = atom({ title: 'Hello', body: '' });
const titleAtom = atom((get) => get(articleAtom).title);
function Title() {
const title = useAtomValue(titleAtom);
return <h3>{title}</h3>;
}
export default function App() {
const setArticle = useSetAtom(articleAtom);
return (
<>
<Title />
<button
onClick={() =>
setArticle((i) => ({
...i,
body: i.body + '!',
}))
}
>
body + !
</button>
<button
onClick={() =>
setArticle((i) => ({
...i,
title: i.title + '!',
}))
}
>
title + !
</button>
</>
);
}restate مرّر selector إلى الخطّاف نفسه الذي تستخدمه بالفعل.
import { restate } from '@kkwaenggwari/restate';
const useArticle = restate({ title: 'Hello', body: '' });
function Title() {
const title = useArticle((i) => i.title);
return <h3>{title}</h3>;
}
export default function App() {
return (
<>
<Title />
<button
onClick={() =>
useArticle.set((i) => ({
...i,
body: i.body + '!',
}))
}
>
body + !
</button>
<button
onClick={() =>
useArticle.set((i) => ({
...i,
title: i.title + '!',
}))
}
>
title + !
</button>
</>
);
}كل الأدوات هنا تستطيع الاختيار. يستخدم restate استدعاء الخطّاف نفسه بدل خطّاف selector منفصل أو atom مشتق.
حفظ مسودة
استعد مسودة محفوظة عند البدء، واحفظ عند النقر، واعرض حالات الحفظ الجاري وعدم الحفظ والفشل.
Redux Toolkit ينظّم التحديثات في slice وaction تُرسَل إلى مخزن يُوفَّر عبر سياق React. أدوات DevTools ممتازة وتدفّق تحديث صارم ومتوقّع للفرق الكبيرة.
Zustand ينشئ خطّاف مخزن عبر create() ويغيّره عبر setState. صغير جدًا ومرن، مع middleware للحفظ وDevTools.
Jotai يركّب atom وatom مشتقة، اختياريًا داخل Provider. تبعيات دقيقة بين قطع حالة صغيرة كثيرة.
import { useEffect, useState } from 'react';
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useSelector } from 'react-redux';
function openDraftDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('draft-example', 1);
let blocked = false;
request.onupgradeneeded = () => {
request.result.createObjectStore('drafts');
};
request.onblocked = () => {
blocked = true;
reject(new Error('Close other tabs to upgrade storage'));
};
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const db = request.result;
if (blocked) {
db.close();
return;
}
db.onversionchange = () => db.close();
resolve(db);
};
});
}
async function storedDraft(mode, text) {
const db = await openDraftDB();
try {
return await new Promise((resolve, reject) => {
const tx = db.transaction('drafts', mode);
const store = tx.objectStore('drafts');
const request = mode === 'readonly' ? store.get('draft') : store.put(text, 'draft');
tx.oncomplete = () => resolve(request.result);
tx.onabort = () => reject(tx.error || new Error('Storage aborted'));
});
} finally {
db.close();
}
}
const draft = createSlice({
name: 'draft',
initialState: '',
reducers: { edit: (_, action) => action.payload },
});
const store = configureStore({ reducer: { draft: draft.reducer } });
const hydrated = storedDraft('readonly').then((saved) => {
if (saved === undefined) return;
if (typeof saved !== 'string') throw new Error('Invalid draft');
const value = saved;
store.dispatch(draft.actions.edit(value));
});
// Observe errors immediately, even before the component mounts.
hydrated.catch(() => {});
function DraftEditor() {
const text = useSelector((state) => state.draft);
const [ready, setReady] = useState(false);
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState('Loading');
useEffect(() => {
let active = true;
hydrated.then(
() => {
if (active) {
setReady(true);
setMessage('Ready');
}
},
() => {
if (active) setMessage('Storage error');
},
);
return () => {
active = false;
};
}, []);
async function save() {
setSaving(true);
try {
await storedDraft('readwrite', store.getState().draft);
setMessage('Saved');
} catch {
setMessage('Save failed');
} finally {
setSaving(false);
}
}
return (
<>
<label>
Draft
<textarea
value={text}
disabled={!ready || saving}
onChange={(event) => {
const value = event.target.value;
store.dispatch(draft.actions.edit(value));
setMessage('Edited');
}}
/>
</label>
<output>{text}</output>
<button disabled={!ready || saving} onClick={save}>
Save
</button>
<p>{message}</p>
</>
);
}
export default function App() {
return (
<Provider store={store}>
<DraftEditor />
</Provider>
);
}import { useEffect, useState } from 'react';
import { create } from 'zustand';
function openDraftDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('draft-example', 1);
let blocked = false;
request.onupgradeneeded = () => {
request.result.createObjectStore('drafts');
};
request.onblocked = () => {
blocked = true;
reject(new Error('Close other tabs to upgrade storage'));
};
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const db = request.result;
if (blocked) {
db.close();
return;
}
db.onversionchange = () => db.close();
resolve(db);
};
});
}
async function storedDraft(mode, text) {
const db = await openDraftDB();
try {
return await new Promise((resolve, reject) => {
const tx = db.transaction('drafts', mode);
const store = tx.objectStore('drafts');
const request = mode === 'readonly' ? store.get('draft') : store.put(text, 'draft');
tx.oncomplete = () => resolve(request.result);
tx.onabort = () => reject(tx.error || new Error('Storage aborted'));
});
} finally {
db.close();
}
}
const useDraft = create(() => ({ text: '' }));
const hydrated = storedDraft('readonly').then((saved) => {
if (saved === undefined) return;
if (typeof saved !== 'string') throw new Error('Invalid draft');
const value = saved;
useDraft.setState({ text: value });
});
// Observe errors immediately, even before the component mounts.
hydrated.catch(() => {});
function DraftEditor() {
const text = useDraft((state) => state.text);
const [ready, setReady] = useState(false);
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState('Loading');
useEffect(() => {
let active = true;
hydrated.then(
() => {
if (active) {
setReady(true);
setMessage('Ready');
}
},
() => {
if (active) setMessage('Storage error');
},
);
return () => {
active = false;
};
}, []);
async function save() {
setSaving(true);
try {
await storedDraft('readwrite', useDraft.getState().text);
setMessage('Saved');
} catch {
setMessage('Save failed');
} finally {
setSaving(false);
}
}
return (
<>
<label>
Draft
<textarea
value={text}
disabled={!ready || saving}
onChange={(event) => {
const value = event.target.value;
useDraft.setState({ text: value });
setMessage('Edited');
}}
/>
</label>
<output>{text}</output>
<button disabled={!ready || saving} onClick={save}>
Save
</button>
<p>{message}</p>
</>
);
}
export default function App() {
return <DraftEditor />;
}import { useEffect, useState } from 'react';
import { atom, createStore, useAtomValue } from 'jotai';
function openDraftDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('draft-example', 1);
let blocked = false;
request.onupgradeneeded = () => {
request.result.createObjectStore('drafts');
};
request.onblocked = () => {
blocked = true;
reject(new Error('Close other tabs to upgrade storage'));
};
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const db = request.result;
if (blocked) {
db.close();
return;
}
db.onversionchange = () => db.close();
resolve(db);
};
});
}
async function storedDraft(mode, text) {
const db = await openDraftDB();
try {
return await new Promise((resolve, reject) => {
const tx = db.transaction('drafts', mode);
const store = tx.objectStore('drafts');
const request = mode === 'readonly' ? store.get('draft') : store.put(text, 'draft');
tx.oncomplete = () => resolve(request.result);
tx.onabort = () => reject(tx.error || new Error('Storage aborted'));
});
} finally {
db.close();
}
}
const draftAtom = atom('');
const store = createStore();
const hydrated = storedDraft('readonly').then((saved) => {
if (saved === undefined) return;
if (typeof saved !== 'string') throw new Error('Invalid draft');
const value = saved;
store.set(draftAtom, value);
});
// Observe errors immediately, even before the component mounts.
hydrated.catch(() => {});
function DraftEditor() {
const text = useAtomValue(draftAtom, { store });
const [ready, setReady] = useState(false);
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState('Loading');
useEffect(() => {
let active = true;
hydrated.then(
() => {
if (active) {
setReady(true);
setMessage('Ready');
}
},
() => {
if (active) setMessage('Storage error');
},
);
return () => {
active = false;
};
}, []);
async function save() {
setSaving(true);
try {
await storedDraft('readwrite', store.get(draftAtom));
setMessage('Saved');
} catch {
setMessage('Save failed');
} finally {
setSaving(false);
}
}
return (
<>
<label>
Draft
<textarea
value={text}
disabled={!ready || saving}
onChange={(event) => {
const value = event.target.value;
store.set(draftAtom, value);
setMessage('Edited');
}}
/>
</label>
<output>{text}</output>
<button disabled={!ready || saving} onClick={save}>
Save
</button>
<p>{message}</p>
</>
);
}
export default function App() {
return <DraftEditor />;
}restate يستعيد persistedRestate تلقائيًا، ويُبلغ useStatus عن الحفظ والتعديلات غير المحفوظة والأخطاء.
import { persistedRestate } from '@kkwaenggwari/restate';
const useDraft = persistedRestate('', {
key: 'draft',
validate: (value) => typeof value === 'string',
});
export default function App() {
const text = useDraft();
const { dirty, operation, error } = useDraft.useStatus();
const saving = operation === 'save';
const message = error ? 'Save failed' : saving ? 'Saving…' : dirty ? 'Edited' : 'Saved';
return (
<>
<label>
Draft
<textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />
</label>
<output>{text}</output>
<button disabled={saving} onClick={() => useDraft.save().catch(() => {})}>
Save
</button>
<p>{message}</p>
</>
);
}تكتب النسخ الأخرى شيفرة IndexedDB نفسها يدويًا. يمكن لـ middleware الحفظ أو أدوات التخزين لديها اختصارها بمحوّل؛ وتلك الشيفرة غير محسوبة هنا.
حالة لكل مستند
مستندان يحتفظ كل منهما بحالة مستقلة، وإغلاق أحدهما يحرّرها.
Redux Toolkit ينظّم التحديثات في slice وaction تُرسَل إلى مخزن يُوفَّر عبر سياق React. أدوات DevTools ممتازة وتدفّق تحديث صارم ومتوقّع للفرق الكبيرة.
Zustand ينشئ خطّاف مخزن عبر create() ويغيّره عبر setState. صغير جدًا ومرن، مع middleware للحفظ وDevTools.
Jotai يركّب atom وatom مشتقة، اختياريًا داخل Provider. تبعيات دقيقة بين قطع حالة صغيرة كثيرة.
import { useState } from 'react';
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useSelector } from 'react-redux';
const draft = createSlice({
name: 'draft',
initialState: '',
reducers: { edit: (_, action) => action.payload },
});
const selectionSlice = createSlice({
name: 'selection',
initialState: 0,
reducers: { next: (index) => index + 1 },
});
function createDocument() {
return {
store: configureStore({
reducer: {
draft: draft.reducer,
selection: selectionSlice.reducer,
},
}),
};
}
function Document({ doc, name }) {
const text = useSelector((state) => state.draft);
const selection = useSelector((state) => state.selection);
return (
<section>
<label>
{name}
<input
value={text}
onChange={(event) => doc.store.dispatch(draft.actions.edit(event.target.value))}
/>
</label>
<output>{text}</output>
<button onClick={() => doc.store.dispatch(selectionSlice.actions.next())}>
Selection: {selection}
</button>
</section>
);
}
function DocumentSlot({ name }) {
const [doc] = useState(createDocument);
return (
<Provider store={doc.store}>
<Document doc={doc} name={name} />
</Provider>
);
}
export default function App() {
const [leftOpen, setLeftOpen] = useState(true);
return (
<>
<button onClick={() => setLeftOpen((open) => !open)}>
{leftOpen ? 'Close left' : 'Open left'}
</button>
{leftOpen && <DocumentSlot name="Left" />}
<DocumentSlot name="Right" />
</>
);
}import { useState } from 'react';
import { useStore } from 'zustand';
import { createStore } from 'zustand/vanilla';
function createDocument() {
return {
draft: createStore(() => ({ text: '' })),
selection: createStore(() => ({ index: 0 })),
};
}
function Document({ doc, name }) {
const text = useStore(doc.draft, (state) => state.text);
const selection = useStore(doc.selection, (state) => state.index);
return (
<section>
<label>
{name}
<input
value={text}
onChange={(event) => doc.draft.setState({ text: event.target.value })}
/>
</label>
<output>{text}</output>
<button onClick={() => doc.selection.setState((s) => ({ index: s.index + 1 }))}>
Selection: {selection}
</button>
</section>
);
}
function DocumentSlot({ name }) {
const [doc] = useState(createDocument);
return <Document doc={doc} name={name} />;
}
export default function App() {
const [leftOpen, setLeftOpen] = useState(true);
return (
<>
<button onClick={() => setLeftOpen((open) => !open)}>
{leftOpen ? 'Close left' : 'Open left'}
</button>
{leftOpen && <DocumentSlot name="Left" />}
<DocumentSlot name="Right" />
</>
);
}import { useState } from 'react';
import { atom, createStore, useAtomValue } from 'jotai';
const draftAtom = atom('');
const selectionAtom = atom(0);
function createDocument() {
return { store: createStore() };
}
function Document({ doc, name }) {
const text = useAtomValue(draftAtom, { store: doc.store });
const selection = useAtomValue(selectionAtom, { store: doc.store });
return (
<section>
<label>
{name}
<input value={text} onChange={(event) => doc.store.set(draftAtom, event.target.value)} />
</label>
<output>{text}</output>
<button onClick={() => doc.store.set(selectionAtom, (n) => n + 1)}>
Selection: {selection}
</button>
</section>
);
}
function DocumentSlot({ name }) {
const [doc] = useState(createDocument);
return <Document doc={doc} name={name} />;
}
export default function App() {
const [leftOpen, setLeftOpen] = useState(true);
return (
<>
<button onClick={() => setLeftOpen((open) => !open)}>
{leftOpen ? 'Close left' : 'Open left'}
</button>
{leftOpen && <DocumentSlot name="Left" />}
<DocumentSlot name="Right" />
</>
);
}restate ينشئ useRestateScope المخازن ويتلفها بعد الإزالة.
import { useState } from 'react';
import { restate, useRestateScope } from '@kkwaenggwari/restate';
function Document({ name }) {
const doc = useRestateScope((scope) => ({
useDraft: restate('', { scope }),
useSelection: restate(0, { scope }),
}));
const text = doc.useDraft();
const selection = doc.useSelection();
return (
<section>
<label>
{name}
<input value={text} onChange={(event) => doc.useDraft.set(event.target.value)} />
</label>
<output>{text}</output>
<button onClick={() => doc.useSelection.set((n) => n + 1)}>Selection: {selection}</button>
</section>
);
}
export default function App() {
const [leftOpen, setLeftOpen] = useState(true);
return (
<>
<button onClick={() => setLeftOpen((open) => !open)}>
{leftOpen ? 'Close left' : 'Open left'}
</button>
{leftOpen && <Document name="Left" />}
<Document name="Right" />
</>
);
}يمكن أيضًا لـ Provider أو store في Jotai ولمخازن Zustand المجرّدة عزل الحالة؛ لكنك تنشئها وتوصلها بنفسك.
كيف تُحسب هذه الأرقام
الأسطر غير الفارغة في الشيفرة المعروضة، والأسماء المستوردة من كل مكتبة حالة (لا تُحسب استيرادات React). كل نسخة تعرض UI نفسها بالتخزين نفسه وحالات التحميل والخطأ نفسها. تفترض أمثلة restate وجود await restateManager.ready في نقطة دخول التطبيق. رُوجعت المصادر مقابل التوثيق الرسمي لكل مشروع:
- Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. اختره حين يكون الأهم سجل action واحد قابل للتدقيق ومنظومة middleware كبيرة
- Zustand — Zustand · Introduction, Zustand · persist. اختره حين تريد أصغر نواة ممكنة وتفضّل تركيب middleware
- Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. اختره حين تكون حالتك بطبيعتها شبكة من قيم مشتقة صغيرة
02 — سريع
اعرض ما تغيّر فقط. واكتب مرة واحدة.
تشغّل اللوحتان حزمة restate الحقيقية في متصفحك. انقر واكتب وراقب العدّادات.
خريطة العرض
الحقول الأربعة نفسها تُقرأ بطريقتين. غيّر حقلًا وعدّ مرات العرض.
- الاسم
- السمة
- العدّاد
- المسودة
- الاسم
- السمة
- العدّاد
- المسودة
كتابات مدمجة
ينتظر autoSave توقّفًا في الكتابة ثم يكتب مرة واحدة. ودفعة من استدعاءات save() تتشارك كتابة واحدة في الطابور.
persistedRestate('', { key: 'note', autoSave: { delayMs: 400 } });الأرقام مأخوذة من عمليات عرض React حقيقية ومن محوّل تخزين يعدّ الكتابات في هذه الصفحة. المكتبات الأخرى تستطيع اختيار القيم أيضًا؛ هنا الاختيار هو ببساطة طريقة القراءة.
03 — مستقر
عندما يتعطّل التخزين، تبقى بياناتك في مكانها.
الحفظ هو اللحظة التي تضيع فيها الحالة: أقراص بطيئة، وتخزين محظور، وعلامات تبويب مغلقة. هذه ضمانات في restate 1.0، لا نصائح.
- 01
عمليات الحفظ تتم بالترتيب
ينفّذ كل مخزن عمليات التخزين واحدة تلو الأخرى، فلا يسمح الحفظ البطيء أبدًا لقيمة قديمة بأن تكتب فوق قيمة أحدث.
save() · restore() - 02
الحفظ المنتظر يأخذ أحدث قيمة
استدعاء save() بينما ينتظر حفظ آخر في الطابور ينضم إليه بدل أن يكدّس كتابات إضافية.
save(): Promise<T> - 03
السجلات غير المقروءة لا تُستبدل خطأً أبدًا
إذا فشل تحميل سجل، فلا يلمسه autoSave ولا الحفظ الجماعي، ويُبلغان عن LOAD_FAILED. وحده save() الصريح يمكنه استبداله.
LOAD_FAILED - 04
حتى بعد الإزالة، يكتمل الحفظ
تكتمل عمليات الحفظ المطلوبة قبل الإزالة. أما الكتابات اللاحقة إلى مخزن مُتلَف فتُتجاهل، كما في حالة React بعد الإزالة.
useRestateScope - 05
لا تخزين؟ يبدأ التطبيق مع ذلك
أثناء العرض على الخادم أو مع تخزين محظور، تواصل المخازن العمل في الذاكرة وتُبلغ بأن التخزين غير متاح.
status().available - 06
الأخطاء تخبرك بالماذا والأين
يحمل كل خطأ في restate رمزًا ذا نوع ومفتاح سجله، لتعرض الرسالة المناسبة.
error.code · error.key
ينمو مع تطبيقك
غيّر التعريف. واحتفظ بمكوّناتك.
الحفظ والحفظ التلقائي والتحقق خيارات للمخزن نفسه. المكوّن على اليمين لا يتغيّر أبدًا.
الذاكرة
قيمة مشتركة في الذاكرة.
import { restate } from '@kkwaenggwari/restate'; export const useDraft = restate('');محفوظ
يُستعاد تلقائيًا؛ ويُكتب عندما تستدعي save().
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft' });حفظ تلقائي
يُكتب مرة واحدة بعد توقّف عن الكتابة لمدة 500 ms.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft', autoSave: { delayMs: 500 }, });مع تحقّق
تُرحَّل السجلات القديمة وتُفحص قبل الاستخدام.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft', autoSave: { delayMs: 500 }, version: 2, migrate: (value) => String(value ?? ''), validate: (value) => typeof value === 'string', });
المكوّن — نفسه في كل خطوة
function Editor() {
const text = useDraft();
return <textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />;
}يعمل حيث يعمل React
حزمة واحدة لكل بيئة React.
react >=16.8.0React
يعمل مع React 16.8 وما بعده، في المتصفح وأثناء العرض على الخادم.
react-native-mmkv >=2.0.0React Native
الاستيرادات نفسها. يستخدم الحفظ MMKV بمجرد تثبيت react-native-mmkv.
SSRالعرض على الخادم
تأخذ مخازن المكوّن لقطة الخادم من props وتبدأ التحميل بعد الإماهة.
index.d.tsTypeScript
التعريفات مضمّنة. المحدِّدات وحقول الحالة ورموز الأخطاء كلها ذات أنواع.
البداية
أول حالة مشتركة لك في دقيقة.
ثبّت، عرّف، استخدم. وأضف الحفظ حين يحتاجه تطبيقك.
- 01التثبيت
npm install @kkwaenggwari/restate - 02عرّف مخزنًا
export const useCart = restate({ items: 0, total: 0 }); - 03اقرأه في أي مكوّن
const items = useCart((cart) => cart.items);
KKWAENGGWARI