מצב ל־React ול־React Native · 1.0

שתפו מצב.hook אחד.זהו.

הצהירו על מאגר בשורה אחת והשתמשו בו בכל רכיב, בלי 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 שנשלחים למאגר שמסופק דרך context של React. DevTools מצוינים וזרימת עדכונים קפדנית וצפויה לצוותים גדולים.

Zustand יוצר hook של מאגר עם 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 הצהרה אחת היא גם המאגר וגם ה־hook.

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 שנשלחים למאגר שמסופק דרך context של React. DevTools מצוינים וזרימת עדכונים קפדנית וצפויה לצוותים גדולים.

Zustand יוצר hook של מאגר עם 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 לאותו hook שאתם כבר משתמשים בו.

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 משתמש באותה קריאה ל־hook במקום hook נפרד ל־selector או atom נגזר.

שמירת טיוטה

שחזור טיוטה שמורה בהפעלה, שמירה בלחיצה והצגת מצבי שמירה, לא נשמר ונכשל.

Redux Toolkit מארגן עדכונים ב־slice וב־action שנשלחים למאגר שמסופק דרך context של React. DevTools מצוינים וזרימת עדכונים קפדנית וצפויה לצוותים גדולים.

Zustand יוצר hook של מאגר עם 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 שנשלחים למאגר שמסופק דרך context של React. DevTools מצוינים וזרימת עדכונים קפדנית וצפויה לצוותים גדולים.

Zustand יוצר hook של מאגר עם 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 ומאגרי vanilla של 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 ומתחילים לטעון אחרי ה־hydration.

  • 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

פרויקטים

חיפוש בתיעוד

חיפוש בכותרות, בתיאורים ובטקסט של כל המדריכים.