Stare pentru React și React Native · 1.0

Partajați starea.Un hook.Gata.

Declarați un store într-o linie și folosiți-l în orice componentă, fără Provider, action-uri și reducer-e. O componentă se re-randează doar când se schimbă valoarea pe care a selectat-o, iar salvarea are loc când decideți.

Terminal
npm install @kkwaenggwari/restate

Live — un store restate real

useCart.set(cart => ({ ...cart, items: cart.items + 1, total: cart.total + 12 }))

items și total se schimbă: trei carduri se randează, cardul cadou rămâne pe loc.

01 — Simplu

Aceeași funcționalitate, mai puține concepte.

Patru implementări ale aceleiași sarcini, una lângă alta. Cifrele provin din codul afișat aici și sunt măsurate la construirea acestui site.

Contor partajat

Două componente citesc și actualizează același număr.

Redux Toolkit Organizează actualizările în slice-uri și action-uri trimise unui store furnizat prin contextul React. DevTools excelente și un flux de actualizare strict și previzibil pentru echipe mari.

Zustand Creează un hook de store cu create() și îl modifică prin setState. Minuscul și flexibil, cu middleware pentru persistență și DevTools.

Jotai Compune atomi și atomi derivați, opțional într-un Provider. Dependențe fine între multe bucăți mici de stare.

Redux Toolkit29 linii · 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 linii · 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 linii · 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 O singură declarație este atât store, cât și hook.

restate13 linii · 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 />
    </>
  );
}

Pentru această sarcină, și Zustand și Jotai sunt mici și fără Provider. Diferența crește odată cu salvarea și starea per document.

Selectarea unui câmp

Componenta de titlu nu ar trebui să se randeze când se schimbă doar textul.

Redux Toolkit Organizează actualizările în slice-uri și action-uri trimise unui store furnizat prin contextul React. DevTools excelente și un flux de actualizare strict și previzibil pentru echipe mari.

Zustand Creează un hook de store cu create() și îl modifică prin setState. Minuscul și flexibil, cu middleware pentru persistență și DevTools.

Jotai Compune atomi și atomi derivați, opțional într-un Provider. Dependențe fine între multe bucăți mici de stare.

Redux Toolkit26 linii · 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 linii · 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 linii · 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 Dați un selector aceluiași hook pe care îl folosiți deja.

restate17 linii · 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>
    </>
  );
}

Toate uneltele de aici pot selecta. restate folosește același apel de hook în locul unui hook de selector separat sau al unui atom derivat.

Salvarea unei ciorne

La pornire restaurați o ciornă salvată, salvați la clic și afișați stările de salvare, nesalvat și eșuat.

Redux Toolkit Organizează actualizările în slice-uri și action-uri trimise unui store furnizat prin contextul React. DevTools excelente și un flux de actualizare strict și previzibil pentru echipe mari.

Zustand Creează un hook de store cu create() și îl modifică prin setState. Minuscul și flexibil, cu middleware pentru persistență și DevTools.

Jotai Compune atomi și atomi derivați, opțional într-un Provider. Dependențe fine între multe bucăți mici de stare.

Redux Toolkit84 linii · 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 linii · 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 linii · 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 restaurează automat, iar useStatus raportează salvarea, modificările nesalvate și erorile.

restate18 linii · 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>
    </>
  );
}

Celelalte versiuni scriu manual același cod IndexedDB. Middleware-ul lor de persistență sau uneltele de stocare îl pot scurta cu un adaptor; acel cod nu se numără aici.

Stare per document

Două documente păstrează stări independente, iar închiderea unuia o eliberează.

Redux Toolkit Organizează actualizările în slice-uri și action-uri trimise unui store furnizat prin contextul React. DevTools excelente și un flux de actualizare strict și previzibil pentru echipe mari.

Zustand Creează un hook de store cu create() și îl modifică prin setState. Minuscul și flexibil, cu middleware pentru persistență și DevTools.

Jotai Compune atomi și atomi derivați, opțional într-un Provider. Dependențe fine între multe bucăți mici de stare.

Redux Toolkit42 linii · 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 linii · 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 linii · 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 creează store-urile și le distruge după demontare.

restate27 linii · 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" />
    </>
  );
}

Un Provider sau un store în Jotai și store-urile vanilla în Zustand pot și ele izola starea; le creați și le conectați singuri.

Cum se calculează aceste cifre

Liniile nevide ale codului afișat și numele importate din fiecare bibliotecă de stare (importurile React nu se numără). Fiecare versiune randează același UI cu aceeași stocare și aceleași stări de încărcare și eroare. Exemplele restate presupun await restateManager.ready în punctul de intrare al aplicației. Sursele au fost verificate cu documentația oficială a fiecărui proiect:

  • Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Alegeți-l când contează cel mai mult un jurnal de action-uri auditabil și un ecosistem mare de middleware
  • Zustand — Zustand · Introduction, Zustand · persist. Alegeți-l când vreți cel mai mic nucleu și preferați să compuneți middleware
  • Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Alegeți-l când starea este, prin natura ei, un graf de valori derivate mici

02 — Rapid

Randați doar ce s-a schimbat. Scrieți o dată.

Ambele panouri rulează pachetul restate real în browserul dumneavoastră. Dați clic, tastați și urmăriți contoarele.

Harta randărilor

Aceleași patru câmpuri, citite în două feluri. Schimbați un câmp și numărați randările.

Citește tot store-ul
  • Nume
  • Temă
  • Contor
  • Ciornă
Selectează un câmp
  • Nume
  • Temă
  • Contor
  • Ciornă

Scrieri comasate

autoSave așteaptă o pauză în tastare, apoi scrie o dată. O rafală de apeluri save() împarte o singură scriere din coadă.

persistedRestate('', { key: 'note', autoSave: { delayMs: 400 } });

Cifrele provin din randări React reale și dintr-un adaptor de stocare care numără scrierile pe această pagină. Și alte biblioteci pot selecta valori; aici, selecția este doar un mod de a citi.

03 — Stabil

Când stocarea cedează, datele rămân la locul lor.

Starea se pierde exact la salvare: discuri lente, stocare blocată, file închise. Acestea sunt garanțiile restate 1.0, nu sfaturi.

Deschideți laboratorul de defecte Citiți ghidul de fiabilitate
  1. 01

    Salvările rulează în ordine

    Fiecare store execută operațiunile de stocare una câte una, așa că o salvare lentă nu lasă niciodată o valoare veche să o suprascrie pe una mai nouă.

    save() · restore()
  2. 02

    O salvare în așteptare preia cea mai nouă valoare

    Un apel save() făcut cât altă salvare e în coadă se alătură acesteia în loc să adune scrieri în plus.

    save(): Promise<T>
  3. 03

    Înregistrările necitite nu sunt niciodată înlocuite din greșeală

    Dacă o înregistrare nu s-a încărcat, autoSave și salvările în bloc nu o ating și raportează LOAD_FAILED. Doar un save() explicit o poate înlocui.

    LOAD_FAILED
  4. 04

    Salvarea se încheie și după demontare

    Salvările cerute înainte de demontare se încheie. Scrierile ulterioare într-un store distrus sunt ignorate, ca starea React după demontare.

    useRestateScope
  5. 05

    Nu există stocare? Aplicația pornește oricum

    În timpul randării pe server sau când stocarea e blocată, store-urile continuă să funcționeze în memorie și raportează că stocarea nu e disponibilă.

    status().available
  6. 06

    Erorile spun ce și unde

    Fiecare eroare restate are un cod tipizat și key-ul înregistrării, ca să puteți afișa mesajul potrivit.

    error.code · error.key

Crește odată cu aplicația

Schimbați declarația. Păstrați componentele.

Salvarea, salvarea automată și validarea sunt opțiuni ale aceluiași store. Componenta din dreapta nu se schimbă niciodată.

  1. Memorie

    O valoare partajată în memorie.

    import { restate } from '@kkwaenggwari/restate';
    
    export const useDraft = restate('');
  2. Salvat

    Se restaurează automat; se scrie când apelați save().

    import { persistedRestate } from '@kkwaenggwari/restate';
    
    export const useDraft = persistedRestate('', { key: 'draft' });
  3. Salvare automată

    Se scrie o dată după o pauză de 500 ms în tastare.

    import { persistedRestate } from '@kkwaenggwari/restate';
    
    export const useDraft = persistedRestate('', {
      key: 'draft',
      autoSave: { delayMs: 500 },
    });
  4. Validat

    Înregistrările mai vechi sunt migrate și verificate înainte de folosire.

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

Componentă — aceeași la fiecare pas

function Editor() {
  const text = useDraft();

  return <textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />;
}

Rulează oriunde rulează React

Un singur pachet pentru fiecare mediu React.

  • react >=16.8.0

    React

    Funcționează cu React 16.8 și versiunile mai noi, în browser și la randarea pe server.

  • react-native-mmkv >=2.0.0

    React Native

    Aceleași importuri. Salvarea folosește MMKV imediat ce instalați react-native-mmkv.

  • SSR

    Randare pe server

    Store-urile componentelor iau snapshotul serverului din props și încep încărcarea după hidratare.

  • index.d.ts

    TypeScript

    Declarațiile sunt incluse. Selectorii, câmpurile de status și codurile de eroare sunt tipizate.

Primii pași

Prima dumneavoastră stare partajată într-un minut.

Instalați, declarați, folosiți. Adăugați salvarea când aplicația are nevoie.

  1. 01
    Instalare
    Terminal
    npm install @kkwaenggwari/restate
  2. 02
    Declarați un store
    JavaScript / JSX
    export const useCart = restate({ items: 0, total: 0 });
  3. 03
    Citiți-l în orice componentă
    JavaScript / JSX
    const items = useCart((cart) => cart.items);

KKWAENGGWARI

Proiecte

Căutați în documentație

Căutați în titlurile, descrierile și conținutul tuturor ghidurilor.