Stare pentru React și React Native · 1.0
Partajați starea.Un hook.Gata.
Declarați un store într-o linie și folosiți-l în orice componentă, fără Provider, action-uri și reducer-e. O componentă se re-randează doar când se schimbă valoarea pe care a selectat-o, iar salvarea are loc când decideți.
npm install @kkwaenggwari/restateLive — un store restate real
useCart.set(cart => ({ ...cart, items: cart.items + 1, total: cart.total + 12 }))items și total se schimbă: trei carduri se randează, cardul cadou rămâne pe loc.
- Simplu0Provider, action-uri și reducer-e pentru configurareDeclarați o valoare și folosiți-o. Nimic de conectat dinainte.
- Rapid11 → 1Unsprezece modificări, o scriereautoSave așteaptă o pauză, așa că unsprezece modificări rapide devin o singură scriere.
- Stabil26Coduri de eroare tipizateFiecare eroare spune ce a mers prost și ce înregistrare privește.
01 — Simplu
Aceeași funcționalitate, mai puține concepte.
Patru implementări ale aceleiași sarcini, una lângă alta. Cifrele provin din codul afișat aici și sunt măsurate la construirea acestui site.
Contor partajat
Două componente citesc și actualizează același număr.
Redux Toolkit Organizează actualizările în slice-uri și action-uri trimise unui store furnizat prin contextul React. DevTools excelente și un flux de actualizare strict și previzibil pentru echipe mari.
Zustand Creează un hook de store cu create() și îl modifică prin setState. Minuscul și flexibil, cu middleware pentru persistență și DevTools.
Jotai Compune atomi și atomi derivați, opțional într-un Provider. Dependențe fine între multe bucăți mici de stare.
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 O singură declarație este atât store, cât și 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 />
</>
);
}Pentru această sarcină, și Zustand și Jotai sunt mici și fără Provider. Diferența crește odată cu salvarea și starea per document.
Selectarea unui câmp
Componenta de titlu nu ar trebui să se randeze când se schimbă doar textul.
Redux Toolkit Organizează actualizările în slice-uri și action-uri trimise unui store furnizat prin contextul React. DevTools excelente și un flux de actualizare strict și previzibil pentru echipe mari.
Zustand Creează un hook de store cu create() și îl modifică prin setState. Minuscul și flexibil, cu middleware pentru persistență și DevTools.
Jotai Compune atomi și atomi derivați, opțional într-un Provider. Dependențe fine între multe bucăți mici de stare.
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 Dați un selector aceluiași hook pe care îl folosiți deja.
import { restate } from '@kkwaenggwari/restate';
const useArticle = restate({ title: 'Hello', body: '' });
function Title() {
const title = useArticle((i) => i.title);
return <h3>{title}</h3>;
}
export default function App() {
return (
<>
<Title />
<button
onClick={() =>
useArticle.set((i) => ({
...i,
body: i.body + '!',
}))
}
>
body + !
</button>
<button
onClick={() =>
useArticle.set((i) => ({
...i,
title: i.title + '!',
}))
}
>
title + !
</button>
</>
);
}Toate uneltele de aici pot selecta. restate folosește același apel de hook în locul unui hook de selector separat sau al unui atom derivat.
Salvarea unei ciorne
La pornire restaurați o ciornă salvată, salvați la clic și afișați stările de salvare, nesalvat și eșuat.
Redux Toolkit Organizează actualizările în slice-uri și action-uri trimise unui store furnizat prin contextul React. DevTools excelente și un flux de actualizare strict și previzibil pentru echipe mari.
Zustand Creează un hook de store cu create() și îl modifică prin setState. Minuscul și flexibil, cu middleware pentru persistență și DevTools.
Jotai Compune atomi și atomi derivați, opțional într-un Provider. Dependențe fine între multe bucăți mici de stare.
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 restaurează automat, iar useStatus raportează salvarea, modificările nesalvate și erorile.
import { persistedRestate } from '@kkwaenggwari/restate';
const useDraft = persistedRestate('', {
key: 'draft',
validate: (value) => typeof value === 'string',
});
export default function App() {
const text = useDraft();
const { dirty, operation, error } = useDraft.useStatus();
const saving = operation === 'save';
const message = error ? 'Save failed' : saving ? 'Saving…' : dirty ? 'Edited' : 'Saved';
return (
<>
<label>
Draft
<textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />
</label>
<output>{text}</output>
<button disabled={saving} onClick={() => useDraft.save().catch(() => {})}>
Save
</button>
<p>{message}</p>
</>
);
}Celelalte versiuni scriu manual același cod IndexedDB. Middleware-ul lor de persistență sau uneltele de stocare îl pot scurta cu un adaptor; acel cod nu se numără aici.
Stare per document
Două documente păstrează stări independente, iar închiderea unuia o eliberează.
Redux Toolkit Organizează actualizările în slice-uri și action-uri trimise unui store furnizat prin contextul React. DevTools excelente și un flux de actualizare strict și previzibil pentru echipe mari.
Zustand Creează un hook de store cu create() și îl modifică prin setState. Minuscul și flexibil, cu middleware pentru persistență și DevTools.
Jotai Compune atomi și atomi derivați, opțional într-un Provider. Dependențe fine între multe bucăți mici de stare.
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 creează store-urile și le distruge după demontare.
import { useState } from 'react';
import { restate, useRestateScope } from '@kkwaenggwari/restate';
function Document({ name }) {
const doc = useRestateScope((scope) => ({
useDraft: restate('', { scope }),
useSelection: restate(0, { scope }),
}));
const text = doc.useDraft();
const selection = doc.useSelection();
return (
<section>
<label>
{name}
<input value={text} onChange={(event) => doc.useDraft.set(event.target.value)} />
</label>
<output>{text}</output>
<button onClick={() => doc.useSelection.set((n) => n + 1)}>Selection: {selection}</button>
</section>
);
}
export default function App() {
const [leftOpen, setLeftOpen] = useState(true);
return (
<>
<button onClick={() => setLeftOpen((open) => !open)}>
{leftOpen ? 'Close left' : 'Open left'}
</button>
{leftOpen && <Document name="Left" />}
<Document name="Right" />
</>
);
}Un Provider sau un store în Jotai și store-urile vanilla în Zustand pot și ele izola starea; le creați și le conectați singuri.
Cum se calculează aceste cifre
Liniile nevide ale codului afișat și numele importate din fiecare bibliotecă de stare (importurile React nu se numără). Fiecare versiune randează același UI cu aceeași stocare și aceleași stări de încărcare și eroare. Exemplele restate presupun await restateManager.ready în punctul de intrare al aplicației. Sursele au fost verificate cu documentația oficială a fiecărui proiect:
- Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Alegeți-l când contează cel mai mult un jurnal de action-uri auditabil și un ecosistem mare de middleware
- Zustand — Zustand · Introduction, Zustand · persist. Alegeți-l când vreți cel mai mic nucleu și preferați să compuneți middleware
- Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Alegeți-l când starea este, prin natura ei, un graf de valori derivate mici
02 — Rapid
Randați doar ce s-a schimbat. Scrieți o dată.
Ambele panouri rulează pachetul restate real în browserul dumneavoastră. Dați clic, tastați și urmăriți contoarele.
Harta randărilor
Aceleași patru câmpuri, citite în două feluri. Schimbați un câmp și numărați randările.
- Nume
- Temă
- Contor
- Ciornă
- Nume
- Temă
- Contor
- Ciornă
Scrieri comasate
autoSave așteaptă o pauză în tastare, apoi scrie o dată. O rafală de apeluri save() împarte o singură scriere din coadă.
persistedRestate('', { key: 'note', autoSave: { delayMs: 400 } });Cifrele provin din randări React reale și dintr-un adaptor de stocare care numără scrierile pe această pagină. Și alte biblioteci pot selecta valori; aici, selecția este doar un mod de a citi.
03 — Stabil
Când stocarea cedează, datele rămân la locul lor.
Starea se pierde exact la salvare: discuri lente, stocare blocată, file închise. Acestea sunt garanțiile restate 1.0, nu sfaturi.
- 01
Salvările rulează în ordine
Fiecare store execută operațiunile de stocare una câte una, așa că o salvare lentă nu lasă niciodată o valoare veche să o suprascrie pe una mai nouă.
save() · restore() - 02
O salvare în așteptare preia cea mai nouă valoare
Un apel save() făcut cât altă salvare e în coadă se alătură acesteia în loc să adune scrieri în plus.
save(): Promise<T> - 03
Înregistrările necitite nu sunt niciodată înlocuite din greșeală
Dacă o înregistrare nu s-a încărcat, autoSave și salvările în bloc nu o ating și raportează LOAD_FAILED. Doar un save() explicit o poate înlocui.
LOAD_FAILED - 04
Salvarea se încheie și după demontare
Salvările cerute înainte de demontare se încheie. Scrierile ulterioare într-un store distrus sunt ignorate, ca starea React după demontare.
useRestateScope - 05
Nu există stocare? Aplicația pornește oricum
În timpul randării pe server sau când stocarea e blocată, store-urile continuă să funcționeze în memorie și raportează că stocarea nu e disponibilă.
status().available - 06
Erorile spun ce și unde
Fiecare eroare restate are un cod tipizat și key-ul înregistrării, ca să puteți afișa mesajul potrivit.
error.code · error.key
Crește odată cu aplicația
Schimbați declarația. Păstrați componentele.
Salvarea, salvarea automată și validarea sunt opțiuni ale aceluiași store. Componenta din dreapta nu se schimbă niciodată.
Memorie
O valoare partajată în memorie.
import { restate } from '@kkwaenggwari/restate'; export const useDraft = restate('');Salvat
Se restaurează automat; se scrie când apelați save().
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft' });Salvare automată
Se scrie o dată după o pauză de 500 ms în tastare.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft', autoSave: { delayMs: 500 }, });Validat
Înregistrările mai vechi sunt migrate și verificate înainte de folosire.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft', autoSave: { delayMs: 500 }, version: 2, migrate: (value) => String(value ?? ''), validate: (value) => typeof value === 'string', });
Componentă — aceeași la fiecare pas
function Editor() {
const text = useDraft();
return <textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />;
}Rulează oriunde rulează React
Un singur pachet pentru fiecare mediu React.
react >=16.8.0React
Funcționează cu React 16.8 și versiunile mai noi, în browser și la randarea pe server.
react-native-mmkv >=2.0.0React Native
Aceleași importuri. Salvarea folosește MMKV imediat ce instalați react-native-mmkv.
SSRRandare pe server
Store-urile componentelor iau snapshotul serverului din props și încep încărcarea după hidratare.
index.d.tsTypeScript
Declarațiile sunt incluse. Selectorii, câmpurile de status și codurile de eroare sunt tipizate.
Primii pași
Prima dumneavoastră stare partajată într-un minut.
Instalați, declarați, folosiți. Adăugați salvarea când aplicația are nevoie.
- 01Instalare
npm install @kkwaenggwari/restate - 02Declarați un store
export const useCart = restate({ items: 0, total: 0 }); - 03Citiți-l în orice componentă
const items = useCart((cart) => cart.items);
KKWAENGGWARI