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.
npm install @kkwaenggwari/restateLangsung — 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.
- Sederhana0Provider, action, dan reducer untuk penyiapanDeklarasikan nilai lalu gunakan. Tidak ada yang perlu disambungkan lebih dulu.
- Cepat11 → 1Sebelas perubahan, satu penulisanautoSave menunggu jeda, jadi sebelas perubahan cepat menjadi satu penulisan.
- Stabil26Kode kesalahan bertipeSetiap kesalahan memberi tahu apa yang salah dan catatan mana yang terkait.
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.
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>
);
}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 />
</>
);
}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.
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.
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>
);
}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>
</>
);
}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.
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.
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>
);
}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 />;
}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.
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.
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" />
</>
);
}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" />
</>
);
}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.
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.
- Nama
- Tema
- Penghitung
- Draf
- 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.
- 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() - 02
Penyimpanan tertunda mengambil nilai terbaru
Memanggil save() saat penyimpanan lain sedang mengantre akan bergabung dengannya, bukan menumpuk penulisan tambahan.
save(): Promise<T> - 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 - 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 - 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 - 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.
Memori
Nilai bersama di memori.
import { restate } from '@kkwaenggwari/restate'; export const useDraft = restate('');Tersimpan
Dipulihkan otomatis; ditulis saat Anda memanggil save().
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft' });Simpan otomatis
Ditulis sekali setelah jeda mengetik 500 ms.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft', autoSave: { delayMs: 500 }, });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.0React
Bekerja dengan React 16.8 dan yang lebih baru, di browser maupun saat server rendering.
react-native-mmkv >=2.0.0React Native
Impor yang sama. Penyimpanan memakai MMKV segera setelah Anda menginstal react-native-mmkv.
SSRServer rendering
Store komponen mengambil snapshot server dari props dan mulai memuat setelah hidrasi.
index.d.tsTypeScript
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.
- 01Instal
npm install @kkwaenggwari/restate - 02Deklarasikan store
export const useCart = restate({ items: 0, total: 0 }); - 03Baca di komponen mana pun
const items = useCart((cart) => cart.items);
KKWAENGGWARI