State für React und React Native · 1.0

State teilen.Ein Hook.Fertig.

Deklariere einen Store in einer Zeile und nutze ihn in jeder Komponente – ohne Provider, Actions oder Reducer. Eine Komponente rendert nur, wenn sich der ausgewählte Wert ändert, und gespeichert wird, wann du willst.

Terminal
npm install @kkwaenggwari/restate

Live — ein echter restate-Store

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

items und total ändern sich – drei Karten rendern, die Nachricht bleibt ruhig.

01 — Einfach

Dieselbe Funktion, weniger Konzepte.

Vier Implementierungen derselben Aufgabe nebeneinander. Die Zahlen stammen aus dem hier gezeigten Code und werden beim Build dieser Website gemessen.

Geteilter Zähler

Zwei Komponenten lesen und aktualisieren eine Zahl.

Redux Toolkit Hält Updates in Slices und Actions, die an einen per React-Context bereitgestellten Store dispatcht werden. Hervorragende DevTools und ein strikter, vorhersehbarer Update-Fluss für große Teams.

Zustand Erstellt mit create() einen Store-Hook und ändert ihn mit setState. Winzig und flexibel, mit Middleware für Persistenz und DevTools.

Jotai Kombiniert Atoms und abgeleitete Atoms, optional innerhalb eines Providers. Feingranulare Abhängigkeiten zwischen vielen kleinen State-Stücken.

Redux Toolkit29 Zeilen · 5 APIs
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 Zeilen · 1 APIs
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 Zeilen · 2 APIs
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 Eine Deklaration ist zugleich Store und Hook.

restate13 Zeilen · 1 APIs
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 />
    </>
  );
}

Für diese Aufgabe sind auch Zustand und Jotai klein und ohne Provider. Der Abstand wächst, sobald Speichern und State pro Dokument ins Spiel kommen.

Ein Feld auswählen

Die Titelkomponente darf nicht rendern, wenn sich nur der Text ändert.

Redux Toolkit Hält Updates in Slices und Actions, die an einen per React-Context bereitgestellten Store dispatcht werden. Hervorragende DevTools und ein strikter, vorhersehbarer Update-Fluss für große Teams.

Zustand Erstellt mit create() einen Store-Hook und ändert ihn mit setState. Winzig und flexibel, mit Middleware für Persistenz und DevTools.

Jotai Kombiniert Atoms und abgeleitete Atoms, optional innerhalb eines Providers. Feingranulare Abhängigkeiten zwischen vielen kleinen State-Stücken.

Redux Toolkit26 Zeilen · 4 APIs
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 Zeilen · 1 APIs
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 Zeilen · 3 APIs
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 Übergib einen selector an denselben Hook, den du schon nutzt.

restate17 Zeilen · 1 APIs
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>
    </>
  );
}

Jedes Tool hier kann auswählen. restate nutzt denselben Hook-Aufruf statt eines eigenen selector-Hooks oder eines abgeleiteten Atoms.

Einen Entwurf speichern

Beim Start einen gespeicherten Entwurf wiederherstellen, beim Klick speichern und die Zustände „speichert“, „nicht gespeichert“ und „fehlgeschlagen“ anzeigen.

Redux Toolkit Hält Updates in Slices und Actions, die an einen per React-Context bereitgestellten Store dispatcht werden. Hervorragende DevTools und ein strikter, vorhersehbarer Update-Fluss für große Teams.

Zustand Erstellt mit create() einen Store-Hook und ändert ihn mit setState. Winzig und flexibel, mit Middleware für Persistenz und DevTools.

Jotai Kombiniert Atoms und abgeleitete Atoms, optional innerhalb eines Providers. Feingranulare Abhängigkeiten zwischen vielen kleinen State-Stücken.

Redux Toolkit84 Zeilen · 4 APIs
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 Zeilen · 1 APIs
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 Zeilen · 3 APIs
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 stellt automatisch wieder her, und useStatus meldet Speichervorgang, ungespeicherte Änderungen und Fehler.

restate18 Zeilen · 1 APIs
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>
    </>
  );
}

Die anderen Versionen schreiben denselben IndexedDB-Code von Hand. Ihre Persistenz-Middleware oder Speicher-Utilities können ihn mit einem Adapter verkürzen; dieser Code wird hier nicht gezählt.

State pro Dokument

Zwei Dokumente behalten unabhängigen State, und das Schließen eines Dokuments gibt ihn frei.

Redux Toolkit Hält Updates in Slices und Actions, die an einen per React-Context bereitgestellten Store dispatcht werden. Hervorragende DevTools und ein strikter, vorhersehbarer Update-Fluss für große Teams.

Zustand Erstellt mit create() einen Store-Hook und ändert ihn mit setState. Winzig und flexibel, mit Middleware für Persistenz und DevTools.

Jotai Kombiniert Atoms und abgeleitete Atoms, optional innerhalb eines Providers. Feingranulare Abhängigkeiten zwischen vielen kleinen State-Stücken.

Redux Toolkit42 Zeilen · 4 APIs
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 Zeilen · 2 APIs
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 Zeilen · 3 APIs
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 erstellt die Stores und zerstört sie nach dem Unmount.

restate27 Zeilen · 2 APIs
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" />
    </>
  );
}

Ein Jotai-Provider oder -Store und Vanilla-Stores von Zustand können State ebenfalls isolieren; du erstellst und verbindest sie selbst.

So werden diese Zahlen gezählt

Nicht leere Zeilen des gezeigten Codes und die aus jeder State-Bibliothek importierten Namen (React-Imports werden nicht gezählt). Jede Version rendert dieselbe UI mit demselben Speicher und denselben Lade- und Fehlerzuständen. Die restate-Beispiele setzen await restateManager.ready am Einstiegspunkt der App voraus. Die Quellen wurden anhand der offiziellen Dokumentation des jeweiligen Projekts geprüft:

  • Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Wähle es, wenn ein einziges prüfbares Action-Log und ein großes Middleware-Ökosystem am wichtigsten sind
  • Zustand — Zustand · Introduction, Zustand · persist. Wähle es, wenn du den kleinstmöglichen Kern willst und Middleware lieber selbst kombinierst
  • Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Wähle es, wenn dein State von Natur aus ein Graph kleiner abgeleiteter Werte ist

02 — Schnell

Nur rendern, was sich geändert hat. Nur einmal schreiben.

Beide Panels führen das echte restate-Paket in deinem Browser aus. Klick, tipp und beobachte die Zähler.

Render-Karte

Dieselben vier Felder, auf zwei Arten gelesen. Ändere ein Feld und zähle die Renders.

Liest den ganzen Store
  • Name
  • Theme
  • Zähler
  • Entwurf
Wählt ein Feld aus
  • Name
  • Theme
  • Zähler
  • Entwurf

Zusammengeführte Schreibvorgänge

autoSave wartet auf eine Tipppause und schreibt dann einmal. Eine Salve von save()-Aufrufen teilt sich einen Schreibvorgang in der Warteschlange.

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

Die Zahlen stammen aus echten React-Renders und einem zählenden Speicheradapter auf dieser Seite. Auch andere Bibliotheken können Werte auswählen; hier ist Auswählen einfach die Art zu lesen.

03 — Stabil

Wenn der Speicher versagt, bleiben deine Daten erhalten.

Beim Speichern geht State verloren: langsame Datenträger, blockierter Speicher, geschlossene Tabs. Das hier sind Garantien von restate 1.0, keine Ratschläge.

Fehlerlabor öffnen Guide zur Zuverlässigkeit lesen
  1. 01

    Speichervorgänge laufen der Reihe nach

    Jeder Store führt seine Speicheroperationen nacheinander aus, daher kann ein langsamer Speichervorgang nie einen neueren Wert mit einem älteren überschreiben.

    save() · restore()
  2. 02

    Ein wartender Speichervorgang nimmt den neuesten Wert

    Ein save()-Aufruf, während ein anderer Speichervorgang in der Warteschlange wartet, schließt sich diesem an, statt weitere Schreibvorgänge zu stapeln.

    save(): Promise<T>
  3. 03

    Ungelesene Datensätze werden nie versehentlich überschrieben

    Wenn ein Datensatz nicht geladen werden konnte, lassen autoSave und Sammel-Speichervorgänge ihn in Ruhe und melden LOAD_FAILED. Nur ein explizites save() darf ihn ersetzen.

    LOAD_FAILED
  4. 04

    Unmount – und das Speichern wird trotzdem fertig

    Vor dem Unmount angeforderte Speichervorgänge werden abgeschlossen. Spätere Schreibvorgänge in einen zerstörten Store werden ignoriert, wie React-State nach dem Unmount.

    useRestateScope
  5. 05

    Kein Speicher? Die App startet trotzdem

    Beim Server-Rendering oder bei blockiertem Speicher arbeiten Stores im Arbeitsspeicher weiter und melden, dass der Speicher nicht verfügbar ist.

    status().available
  6. 06

    Fehler sagen, was und wo

    Jeder restate-Fehler trägt einen typisierten Code und den Key seines Datensatzes, damit du die passende Meldung anzeigen kannst.

    error.code · error.key

Wächst mit deiner App

Ändere die Deklaration. Behalte deine Komponenten.

Speichern, automatisches Speichern und Validierung sind Optionen desselben Stores. Die Komponente rechts ändert sich nie.

  1. Arbeitsspeicher

    Ein geteilter Wert im Arbeitsspeicher.

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

    Wird automatisch wiederhergestellt; geschrieben wird beim Aufruf von save().

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

    Wird einmal geschrieben, nachdem das Tippen 500 ms pausiert.

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

    Ältere Datensätze werden vor der Nutzung migriert und geprüft.

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

Komponente — in jedem Schritt unverändert

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

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

Läuft überall, wo React läuft

Ein Paket für jede React-Umgebung.

  • react >=16.8.0

    React

    Funktioniert mit React 16.8 und neuer, im Browser und beim Server-Rendering.

  • react-native-mmkv >=2.0.0

    React Native

    Dieselben Imports. Gespeichert wird mit MMKV, sobald react-native-mmkv installiert ist.

  • SSR

    Server-Rendering

    Komponenteneigene Stores übernehmen ihren Server-Snapshot aus den Props und beginnen nach der Hydration zu laden.

  • index.d.ts

    TypeScript

    Deklarationen inklusive. Selektoren, Statusfelder und Fehlercodes sind typisiert.

Loslegen

Dein erster geteilter State in einer Minute.

Installieren, deklarieren, nutzen. Speichern fügst du hinzu, wenn deine App es braucht.

  1. 01
    Installieren
    Terminal
    npm install @kkwaenggwari/restate
  2. 02
    Einen Store deklarieren
    JavaScript / JSX
    export const useCart = restate({ items: 0, total: 0 });
  3. 03
    In jeder Komponente lesen
    JavaScript / JSX
    const items = useCart((cart) => cart.items);

KKWAENGGWARI

Projekte

Doku durchsuchen

Durchsucht Titel, Beschreibungen und Text aller Guides.