الحالة لـ React وReact Native · 1.0

شارك الحالة.خطّاف واحد.انتهى.

عرّف مخزنًا في سطر واحد واستخدمه في أي مكوّن، من دون Provider أو action أو reducer. لا يُعاد عرض المكوّن إلا عندما تتغيّر القيمة التي اختارها، ويحدث الحفظ حين تقرّر أنت.

Terminal
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. تبعيات دقيقة بين قطع حالة صغيرة كثيرة.

Redux Toolkit29 أسطر · 5 API
JavaScript / JSX
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>
  );
}
Zustand16 أسطر · 1 API
JavaScript / JSX
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 />
    </>
  );
}
Jotai13 أسطر · 2 API
JavaScript / JSX
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 تعريف واحد هو المخزن والخطّاف معًا.

restate13 أسطر · 1 API
JavaScript / JSX
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. تبعيات دقيقة بين قطع حالة صغيرة كثيرة.

Redux Toolkit26 أسطر · 4 API
JavaScript / JSX
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>
  );
}
Zustand17 أسطر · 1 API
JavaScript / JSX
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>
    </>
  );
}
Jotai19 أسطر · 3 API
JavaScript / JSX
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 إلى الخطّاف نفسه الذي تستخدمه بالفعل.

restate17 أسطر · 1 API
JavaScript / JSX
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. تبعيات دقيقة بين قطع حالة صغيرة كثيرة.

Redux Toolkit84 أسطر · 4 API
JavaScript / JSX
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>
  );
}
Zustand79 أسطر · 1 API
JavaScript / JSX
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 />;
}
Jotai80 أسطر · 3 API
JavaScript / JSX
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 عن الحفظ والتعديلات غير المحفوظة والأخطاء.

restate18 أسطر · 1 API
JavaScript / JSX
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. تبعيات دقيقة بين قطع حالة صغيرة كثيرة.

Redux Toolkit42 أسطر · 4 API
JavaScript / JSX
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" />
    </>
  );
}
Zustand35 أسطر · 2 API
JavaScript / JSX
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" />
    </>
  );
}
Jotai33 أسطر · 3 API
JavaScript / JSX
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 المخازن ويتلفها بعد الإزالة.

restate27 أسطر · 2 API
JavaScript / JSX
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، لا نصائح.

افتح مختبر الأعطال اقرأ دليل الموثوقية
  1. 01

    عمليات الحفظ تتم بالترتيب

    ينفّذ كل مخزن عمليات التخزين واحدة تلو الأخرى، فلا يسمح الحفظ البطيء أبدًا لقيمة قديمة بأن تكتب فوق قيمة أحدث.

    save() · restore()
  2. 02

    الحفظ المنتظر يأخذ أحدث قيمة

    استدعاء save() بينما ينتظر حفظ آخر في الطابور ينضم إليه بدل أن يكدّس كتابات إضافية.

    save(): Promise<T>
  3. 03

    السجلات غير المقروءة لا تُستبدل خطأً أبدًا

    إذا فشل تحميل سجل، فلا يلمسه autoSave ولا الحفظ الجماعي، ويُبلغان عن LOAD_FAILED. وحده save() الصريح يمكنه استبداله.

    LOAD_FAILED
  4. 04

    حتى بعد الإزالة، يكتمل الحفظ

    تكتمل عمليات الحفظ المطلوبة قبل الإزالة. أما الكتابات اللاحقة إلى مخزن مُتلَف فتُتجاهل، كما في حالة React بعد الإزالة.

    useRestateScope
  5. 05

    لا تخزين؟ يبدأ التطبيق مع ذلك

    أثناء العرض على الخادم أو مع تخزين محظور، تواصل المخازن العمل في الذاكرة وتُبلغ بأن التخزين غير متاح.

    status().available
  6. 06

    الأخطاء تخبرك بالماذا والأين

    يحمل كل خطأ في restate رمزًا ذا نوع ومفتاح سجله، لتعرض الرسالة المناسبة.

    error.code · error.key

ينمو مع تطبيقك

غيّر التعريف. واحتفظ بمكوّناتك.

الحفظ والحفظ التلقائي والتحقق خيارات للمخزن نفسه. المكوّن على اليمين لا يتغيّر أبدًا.

  1. الذاكرة

    قيمة مشتركة في الذاكرة.

    import { restate } from '@kkwaenggwari/restate';
    
    export const useDraft = restate('');
  2. محفوظ

    يُستعاد تلقائيًا؛ ويُكتب عندما تستدعي save().

    import { persistedRestate } from '@kkwaenggwari/restate';
    
    export const useDraft = persistedRestate('', { key: 'draft' });
  3. حفظ تلقائي

    يُكتب مرة واحدة بعد توقّف عن الكتابة لمدة 500 ms.

    import { persistedRestate } from '@kkwaenggwari/restate';
    
    export const useDraft = persistedRestate('', {
      key: 'draft',
      autoSave: { delayMs: 500 },
    });
  4. مع تحقّق

    تُرحَّل السجلات القديمة وتُفحص قبل الاستخدام.

    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.0

    React

    يعمل مع React 16.8 وما بعده، في المتصفح وأثناء العرض على الخادم.

  • react-native-mmkv >=2.0.0

    React Native

    الاستيرادات نفسها. يستخدم الحفظ MMKV بمجرد تثبيت react-native-mmkv.

  • SSR

    العرض على الخادم

    تأخذ مخازن المكوّن لقطة الخادم من props وتبدأ التحميل بعد الإماهة.

  • index.d.ts

    TypeScript

    التعريفات مضمّنة. المحدِّدات وحقول الحالة ورموز الأخطاء كلها ذات أنواع.

البداية

أول حالة مشتركة لك في دقيقة.

ثبّت، عرّف، استخدم. وأضف الحفظ حين يحتاجه تطبيقك.

  1. 01
    التثبيت
    Terminal
    npm install @kkwaenggwari/restate
  2. 02
    عرّف مخزنًا
    JavaScript / JSX
    export const useCart = restate({ items: 0, total: 0 });
  3. 03
    اقرأه في أي مكوّن
    JavaScript / JSX
    const items = useCart((cart) => cart.items);

KKWAENGGWARI

المشاريع

البحث في التوثيق

ابحث في عناوين جميع الأدلة وأوصافها ونصوصها.