Stav pro React a React Native · 1.0

Sdílejte stav.Jeden hook.Hotovo.

Deklarujte store na jednom řádku a používejte ho v libovolné komponentě, bez Provider, actions a reducerů. Komponenta se překreslí, jen když se změní hodnota, kterou vybrala, a ukládá se, když se rozhodnete.

Terminal
npm install @kkwaenggwari/restate

Živě — skutečný store restate

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

Mění se items a total: překreslí se tři karty, kartička zůstává.

01 — Snadné

Stejná funkce, méně pojmů.

Čtyři implementace stejné úlohy vedle sebe. Čísla pocházejí ze zde zobrazeného kódu a měří se při sestavení tohoto webu.

Sdílené počítadlo

Dvě komponenty čtou a mění stejné číslo.

Redux Toolkit Organizuje aktualizace do slice a actions odesílaných do store poskytovaného přes kontext Reactu. Vynikající DevTools a přísný, předvídatelný tok aktualizací pro velké týmy.

Zustand Vytváří hook store pomocí create() a mění ho přes setState. Drobný a pružný, s middleware pro persistenci a DevTools.

Jotai Skládá atomy a odvozené atomy, volitelně uvnitř Provider. Jemné závislosti mezi mnoha malými kousky stavu.

Redux Toolkit29 řádků · 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 řádků · 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 řádků · 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 Jedna deklarace je store i hook zároveň.

restate13 řádků · 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 />
    </>
  );
}

Pro tuto úlohu jsou i Zustand a Jotai malé a obejdou se bez Provider. Rozdíl roste, když přibude ukládání a stav pro každý dokument.

Výběr pole

Komponenta nadpisu se nemá překreslit, když se změní jen text.

Redux Toolkit Organizuje aktualizace do slice a actions odesílaných do store poskytovaného přes kontext Reactu. Vynikající DevTools a přísný, předvídatelný tok aktualizací pro velké týmy.

Zustand Vytváří hook store pomocí create() a mění ho přes setState. Drobný a pružný, s middleware pro persistenci a DevTools.

Jotai Skládá atomy a odvozené atomy, volitelně uvnitř Provider. Jemné závislosti mezi mnoha malými kousky stavu.

Redux Toolkit26 řádků · 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 řádků · 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 řádků · 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 Předejte selector stejnému hooku, který už používáte.

restate17 řádků · 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>
    </>
  );
}

Vybírat umí všechny nástroje zde. restate používá stejné volání hooku místo samostatného hooku pro selector nebo odvozeného atomu.

Uložení konceptu

Při spuštění obnovte uložený koncept, při kliknutí ukládejte a zobrazte stavy ukládání, neuloženo a selhání.

Redux Toolkit Organizuje aktualizace do slice a actions odesílaných do store poskytovaného přes kontext Reactu. Vynikající DevTools a přísný, předvídatelný tok aktualizací pro velké týmy.

Zustand Vytváří hook store pomocí create() a mění ho přes setState. Drobný a pružný, s middleware pro persistenci a DevTools.

Jotai Skládá atomy a odvozené atomy, volitelně uvnitř Provider. Jemné závislosti mezi mnoha malými kousky stavu.

Redux Toolkit84 řádků · 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 řádků · 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 řádků · 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 obnovuje automaticky a useStatus hlásí ukládání, neuložené úpravy a chyby.

restate18 řádků · 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>
    </>
  );
}

Ostatní verze píší stejný kód IndexedDB ručně. Jejich persistence middleware nebo nástroje úložiště ho mohou zkrátit adaptérem; ten kód se zde nepočítá.

Stav pro každý dokument

Dva dokumenty mají nezávislý stav a zavření jednoho ho uvolní.

Redux Toolkit Organizuje aktualizace do slice a actions odesílaných do store poskytovaného přes kontext Reactu. Vynikající DevTools a přísný, předvídatelný tok aktualizací pro velké týmy.

Zustand Vytváří hook store pomocí create() a mění ho přes setState. Drobný a pružný, s middleware pro persistenci a DevTools.

Jotai Skládá atomy a odvozené atomy, volitelně uvnitř Provider. Jemné závislosti mezi mnoha malými kousky stavu.

Redux Toolkit42 řádků · 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 řádků · 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 řádků · 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 vytváří story a po odpojení je zničí.

restate27 řádků · 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" />
    </>
  );
}

Stav mohou izolovat i Provider nebo store v Jotai a vanilla story v Zustand; vytváříte a propojujete je sami.

Jak se tato čísla počítají

Neprázdné řádky zobrazeného kódu a názvy importované z každé knihovny stavu (importy Reactu se nepočítají). Každá verze renderuje stejné UI se stejným úložištěm a stejnými stavy načítání a chyb. Příklady restate předpokládají await restateManager.ready ve vstupním bodu aplikace. Zdroje byly ověřeny podle oficiální dokumentace každého projektu:

  • Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Zvolte ho, když je nejdůležitější jediný auditovatelný log actions a velký ekosystém middleware
  • Zustand — Zustand · Introduction, Zustand · persist. Zvolte ho, když chcete co nejmenší jádro a raději skládáte middleware
  • Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Zvolte ho, když je váš stav ze své podstaty grafem malých odvozených hodnot

02 — Rychlé

Překreslete jen to, co se změnilo. Zapište jednou.

Oba panely spouštějí skutečný balíček restate ve vašem prohlížeči. Klikejte, pište a sledujte počítadla.

Mapa renderů

Stejná čtyři pole čtená dvěma způsoby. Změňte pole a počítejte rendery.

Čte celý store
  • Jméno
  • Motiv
  • Počítadlo
  • Koncept
Vybírá jedno pole
  • Jméno
  • Motiv
  • Počítadlo
  • Koncept

Sloučené zápisy

autoSave počká na pauzu v psaní a pak zapíše jednou. Série volání save() sdílí jeden zápis ve frontě.

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

Čísla pocházejí ze skutečných renderů Reactu a adaptéru úložiště, který počítá zápisy na této stránce. I jiné knihovny umí vybírat hodnoty; zde je výběr prostě způsob čtení.

03 — Stabilní

Když úložiště selže, vaše data zůstanou na místě.

Stav se ztrácí právě při ukládání: pomalé disky, zablokované úložiště, zavřené karty. Toto jsou záruky restate 1.0, ne rady.

Otevřít laboratoř poruch Číst průvodce spolehlivostí
  1. 01

    Ukládání probíhá v pořadí

    Každý store provádí operace s úložištěm jednu po druhé, takže pomalé uložení nikdy nedovolí staré hodnotě přepsat novější.

    save() · restore()
  2. 02

    Čekající uložení vezme nejnovější hodnotu

    Volání save(), zatímco jiné uložení čeká ve frontě, se k němu připojí, místo aby přidávalo další zápisy.

    save(): Promise<T>
  3. 03

    Nenačtené záznamy se nikdy omylem nepřepíšou

    Pokud se záznam nenačetl, autoSave a hromadná uložení se ho nedotknou a nahlásí LOAD_FAILED. Nahradit ho může jen explicitní save().

    LOAD_FAILED
  4. 04

    I po odpojení se uložení dokončí

    Uložení vyžádaná před odpojením se dokončí. Pozdější zápisy do zničeného store se ignorují, stejně jako stav Reactu po odpojení.

    useRestateScope
  5. 05

    Žádné úložiště? Aplikace se přesto spustí

    Při renderování na serveru nebo se zablokovaným úložištěm story dál fungují v paměti a hlásí, že úložiště není dostupné.

    status().available
  6. 06

    Chyby řeknou co a kde

    Každá chyba restate nese typovaný kód a klíč svého záznamu, takže můžete zobrazit správnou zprávu.

    error.code · error.key

Roste s vaší aplikací

Změňte deklaraci. Komponenty si nechte.

Ukládání, automatické ukládání a validace jsou volby téhož store. Komponenta vpravo se nikdy nemění.

  1. Paměť

    Sdílená hodnota v paměti.

    import { restate } from '@kkwaenggwari/restate';
    
    export const useDraft = restate('');
  2. Ukládaný

    Obnovuje se automaticky; zapíše se, když zavoláte save().

    import { persistedRestate } from '@kkwaenggwari/restate';
    
    export const useDraft = persistedRestate('', { key: 'draft' });
  3. Automatické ukládání

    Zapíše se jednou po 500 ms pauze v psaní.

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

    Starší záznamy se před použitím migrují a kontrolují.

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

Komponenta — v každém kroku stejná

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

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

Funguje všude, kde funguje React

Jeden balíček pro každé prostředí Reactu.

  • react >=16.8.0

    React

    Funguje s React 16.8 a novějším, v prohlížeči i při renderování na serveru.

  • react-native-mmkv >=2.0.0

    React Native

    Stejné importy. Ukládání použije MMKV, jakmile nainstalujete react-native-mmkv.

  • SSR

    Renderování na serveru

    Story komponenty převezmou snímek ze serveru z props a začnou načítat po hydrataci.

  • index.d.ts

    TypeScript

    Deklarace jsou součástí. Selektory, stavová pole a kódy chyb jsou typované.

Začínáme

Váš první sdílený stav za minutu.

Nainstalujte, deklarujte, používejte. Ukládání přidejte, až ho aplikace bude potřebovat.

  1. 01
    Instalace
    Terminal
    npm install @kkwaenggwari/restate
  2. 02
    Deklarujte store
    JavaScript / JSX
    export const useCart = restate({ items: 0, total: 0 });
  3. 03
    Čtěte ho v libovolné komponentě
    JavaScript / JSX
    const items = useCart((cart) => cart.items);

KKWAENGGWARI

Projekty

Hledat v dokumentaci

Hledejte v nadpisech, popisech a textu všech průvodců.