State untuk React dan React Native · 1.0

Bagikan state.Satu hook.Selesai.

Deklarasikan store dalam satu baris dan gunakan di komponen mana pun, tanpa Provider, action, dan reducer. Komponen dirender ulang hanya saat nilai yang dipilihnya berubah, dan penyimpanan terjadi saat Anda memutuskan.

Terminal
npm install @kkwaenggwari/restate

Langsung — store restate sungguhan

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

items dan total berubah: tiga kartu dirender, kartu hadiah tetap diam.

01 — Sederhana

Fitur yang sama, lebih sedikit konsep.

Empat implementasi untuk tugas yang sama, berdampingan. Angka berasal dari kode yang ditampilkan di sini dan diukur saat situs ini di-build.

Penghitung bersama

Dua komponen membaca dan memperbarui angka yang sama.

Redux Toolkit Mengatur pembaruan menjadi slice dan action yang dikirim ke store yang disediakan lewat context React. DevTools yang sangat baik dan alur pembaruan yang ketat serta dapat diprediksi untuk tim besar.

Zustand Membuat hook store dengan create() dan mengubahnya dengan setState. Sangat kecil dan fleksibel, dengan middleware untuk persistensi dan DevTools.

Jotai Menyusun atom dan atom turunan, opsional di dalam Provider. Ketergantungan halus antara banyak potongan state kecil.

Redux Toolkit29 baris · 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 baris · 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 baris · 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 Satu deklarasi sekaligus menjadi store dan hook.

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

Untuk tugas ini Zustand dan Jotai juga kecil dan tanpa Provider. Selisihnya bertambah saat penyimpanan dan state per dokumen ditambahkan.

Memilih field

Komponen judul tidak semestinya dirender saat hanya teksnya yang berubah.

Redux Toolkit Mengatur pembaruan menjadi slice dan action yang dikirim ke store yang disediakan lewat context React. DevTools yang sangat baik dan alur pembaruan yang ketat serta dapat diprediksi untuk tim besar.

Zustand Membuat hook store dengan create() dan mengubahnya dengan setState. Sangat kecil dan fleksibel, dengan middleware untuk persistensi dan DevTools.

Jotai Menyusun atom dan atom turunan, opsional di dalam Provider. Ketergantungan halus antara banyak potongan state kecil.

Redux Toolkit26 baris · 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 baris · 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 baris · 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 Berikan selector ke hook yang sudah Anda pakai.

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

Semua alat di sini bisa memilih. restate memakai panggilan hook yang sama, bukan hook selector terpisah atau atom turunan.

Menyimpan draf

Pulihkan draf tersimpan saat mulai, simpan saat diklik, dan tampilkan state menyimpan, belum tersimpan, dan gagal.

Redux Toolkit Mengatur pembaruan menjadi slice dan action yang dikirim ke store yang disediakan lewat context React. DevTools yang sangat baik dan alur pembaruan yang ketat serta dapat diprediksi untuk tim besar.

Zustand Membuat hook store dengan create() dan mengubahnya dengan setState. Sangat kecil dan fleksibel, dengan middleware untuk persistensi dan DevTools.

Jotai Menyusun atom dan atom turunan, opsional di dalam Provider. Ketergantungan halus antara banyak potongan state kecil.

Redux Toolkit84 baris · 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 baris · 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 baris · 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 memulihkan otomatis, dan useStatus melaporkan penyimpanan, perubahan yang belum tersimpan, dan kesalahan.

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

Versi lain menulis kode IndexedDB yang sama secara manual. Middleware persistensi atau alat penyimpanan mereka dapat mempersingkatnya dengan adaptor; kode itu tidak dihitung di sini.

State per dokumen

Dua dokumen menyimpan state terpisah, dan menutup salah satunya melepaskannya.

Redux Toolkit Mengatur pembaruan menjadi slice dan action yang dikirim ke store yang disediakan lewat context React. DevTools yang sangat baik dan alur pembaruan yang ketat serta dapat diprediksi untuk tim besar.

Zustand Membuat hook store dengan create() dan mengubahnya dengan setState. Sangat kecil dan fleksibel, dengan middleware untuk persistensi dan DevTools.

Jotai Menyusun atom dan atom turunan, opsional di dalam Provider. Ketergantungan halus antara banyak potongan state kecil.

Redux Toolkit42 baris · 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 baris · 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 baris · 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 membuat store dan menghancurkannya setelah unmount.

restate27 baris · 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 atau store di Jotai dan vanilla store di Zustand juga dapat memisahkan state; Anda membuat dan menyambungkannya sendiri.

Cara angka ini dihitung

Baris tidak kosong dari kode yang ditampilkan dan nama yang diimpor dari tiap pustaka state (impor React tidak dihitung). Setiap versi merender UI yang sama dengan penyimpanan yang sama serta state pemuatan dan kesalahan yang sama. Contoh restate mengasumsikan await restateManager.ready di titik masuk aplikasi. Sumber diperiksa terhadap dokumentasi resmi tiap proyek:

  • Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Pilih ini saat satu log action yang bisa diaudit dan ekosistem middleware yang besar paling penting
  • Zustand — Zustand · Introduction, Zustand · persist. Pilih ini saat Anda ingin inti sekecil mungkin dan lebih suka menyusun middleware
  • Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Pilih ini saat state Anda pada dasarnya adalah graf nilai turunan yang kecil

02 — Cepat

Render hanya yang berubah. Tulis sekali.

Kedua panel menjalankan paket restate sungguhan di browser Anda. Klik, ketik, dan perhatikan penghitungnya.

Peta render

Empat field yang sama, dibaca dengan dua cara. Ubah satu field dan hitung render-nya.

Membaca seluruh store
  • Nama
  • Tema
  • Penghitung
  • Draf
Memilih satu field
  • Nama
  • Tema
  • Penghitung
  • Draf

Penulisan yang digabung

autoSave menunggu jeda mengetik lalu menulis sekali. Rentetan panggilan save() berbagi satu penulisan dalam antrean.

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

Angka berasal dari render React sungguhan dan adaptor penyimpanan yang menghitung penulisan di halaman ini. Pustaka lain juga bisa memilih nilai; di sini memilih hanyalah cara membaca.

03 — Stabil

Saat penyimpanan gagal, data Anda tetap di tempatnya.

State hilang justru saat menyimpan: disk lambat, penyimpanan diblokir, tab ditutup. Ini adalah jaminan restate 1.0, bukan saran.

Buka lab kegagalan Baca panduan keandalan
  1. 01

    Penyimpanan berjalan berurutan

    Setiap store menjalankan operasi penyimpanan satu per satu, jadi penyimpanan yang lambat tidak pernah membuat nilai lama menimpa yang lebih baru.

    save() · restore()
  2. 02

    Penyimpanan tertunda mengambil nilai terbaru

    Memanggil save() saat penyimpanan lain sedang mengantre akan bergabung dengannya, bukan menumpuk penulisan tambahan.

    save(): Promise<T>
  3. 03

    Catatan yang belum dibaca tidak pernah tertimpa tanpa sengaja

    Jika sebuah catatan gagal dimuat, autoSave dan penyimpanan massal tidak menyentuhnya dan melaporkan LOAD_FAILED. Hanya save() eksplisit yang dapat menggantinya.

    LOAD_FAILED
  4. 04

    Penyimpanan tetap selesai setelah unmount

    Penyimpanan yang diminta sebelum unmount akan selesai. Penulisan berikutnya ke store yang sudah dihancurkan diabaikan, seperti state React setelah unmount.

    useRestateScope
  5. 05

    Tidak ada penyimpanan? Aplikasi tetap berjalan

    Saat server rendering atau ketika penyimpanan diblokir, store tetap bekerja di memori dan melaporkan bahwa penyimpanan tidak tersedia.

    status().available
  6. 06

    Kesalahan memberi tahu apa dan di mana

    Setiap kesalahan restate membawa kode bertipe dan key catatannya, sehingga Anda bisa menampilkan pesan yang tepat.

    error.code · error.key

Tumbuh bersama aplikasi Anda

Ubah deklarasinya. Komponennya tetap.

Penyimpanan, penyimpanan otomatis, dan validasi adalah opsi dari store yang sama. Komponen di sebelah kanan tidak pernah berubah.

  1. Memori

    Nilai bersama di memori.

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

    Dipulihkan otomatis; ditulis saat Anda memanggil save().

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

    Ditulis sekali setelah jeda mengetik 500 ms.

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

    Catatan lama dimigrasi dan divalidasi sebelum digunakan.

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

Komponen — sama di setiap langkah

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

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

Berjalan di mana pun React berjalan

Satu paket untuk setiap lingkungan React.

  • react >=16.8.0

    React

    Bekerja dengan React 16.8 dan yang lebih baru, di browser maupun saat server rendering.

  • react-native-mmkv >=2.0.0

    React Native

    Impor yang sama. Penyimpanan memakai MMKV segera setelah Anda menginstal react-native-mmkv.

  • SSR

    Server rendering

    Store komponen mengambil snapshot server dari props dan mulai memuat setelah hidrasi.

  • index.d.ts

    TypeScript

    Deklarasi sudah disertakan. Selector, field status, dan kode kesalahan bertipe.

Memulai

State bersama pertama Anda dalam satu menit.

Instal, deklarasikan, gunakan. Tambahkan penyimpanan saat aplikasi membutuhkannya.

  1. 01
    Instal
    Terminal
    npm install @kkwaenggwari/restate
  2. 02
    Deklarasikan store
    JavaScript / JSX
    export const useCart = restate({ items: 0, total: 0 });
  3. 03
    Baca di komponen mana pun
    JavaScript / JSX
    const items = useCart((cart) => cart.items);

KKWAENGGWARI

Proyek

Cari dokumentasi

Cari di judul, deskripsi, dan isi semua panduan.