State för React och React Native · 1.0
Dela state.En hook.Klart.
Deklarera en store på en rad och använd den i vilken komponent som helst, utan Provider, actions eller reducers. En komponent renderas om bara när värdet den valt ändras, och sparande sker när du bestämmer.
npm install @kkwaenggwari/restateLive — en riktig restate-store
useCart.set(cart => ({ ...cart, items: cart.items + 1, total: cart.total + 12 }))items och total ändras: tre kort renderas, presentkortet står still.
- Enkelt0Provider, actions och reducers att sätta uppDeklarera ett värde och använd det. Inget behöver kopplas ihop i förväg.
- Snabbt11 → 1Elva ändringar, en skrivningautoSave väntar på en paus, så elva snabba ändringar blir en enda skrivning.
- Stabilt26Typade felkoderVarje fel berättar vad som gick fel och vilken post det gällde.
01 — Enkelt
Samma funktion, färre begrepp.
Fyra implementationer av samma uppgift sida vid sida. Siffrorna kommer från koden som visas här och mäts när webbplatsen byggs.
Uppgift Delad räknare · Välj ett fält · Spara ett utkast · State per dokument
Delad räknare
Två komponenter läser och uppdaterar samma tal.
Redux Toolkit Organiserar uppdateringar i slices och actions som skickas till en store som tillhandahålls via React-kontext. Utmärkta DevTools och ett strikt, förutsägbart uppdateringsflöde för stora team.
Zustand Skapar en store-hook med create() och ändrar den med setState. Pytteliten och flexibel, med middleware för persistens och DevTools.
Jotai Sätter ihop atoms och härledda atoms, valfritt inuti en Provider. Finkorniga beroenden mellan många små bitar state.
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 En deklaration är både storen och hooken.
import { restate } from '@kkwaenggwari/restate';
const useCount = restate(0);
function Counter() {
const count = useCount();
return <button onClick={() => useCount.set((n) => n + 1)}>{count}</button>;
}
export default function App() {
return (
<>
<Counter />
<Counter />
</>
);
}För den här uppgiften är Zustand och Jotai också små och klarar sig utan Provider. Skillnaden växer när sparande och state per dokument kommer in.
Välj ett fält
Rubrikkomponenten ska inte renderas när bara texten ändras.
Redux Toolkit Organiserar uppdateringar i slices och actions som skickas till en store som tillhandahålls via React-kontext. Utmärkta DevTools och ett strikt, förutsägbart uppdateringsflöde för stora team.
Zustand Skapar en store-hook med create() och ändrar den med setState. Pytteliten och flexibel, med middleware för persistens och DevTools.
Jotai Sätter ihop atoms och härledda atoms, valfritt inuti en Provider. Finkorniga beroenden mellan många små bitar state.
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 Skicka en selector till samma hook som du redan använder.
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>
</>
);
}Alla verktyg här kan välja. restate använder samma hook-anrop i stället för en separat selector-hook eller en härledd atom.
Spara ett utkast
Återställ ett sparat utkast vid start, spara vid klick och visa tillstånden sparar, osparat och misslyckat.
Redux Toolkit Organiserar uppdateringar i slices och actions som skickas till en store som tillhandahålls via React-kontext. Utmärkta DevTools och ett strikt, förutsägbart uppdateringsflöde för stora team.
Zustand Skapar en store-hook med create() och ändrar den med setState. Pytteliten och flexibel, med middleware för persistens och DevTools.
Jotai Sätter ihop atoms och härledda atoms, valfritt inuti en Provider. Finkorniga beroenden mellan många små bitar state.
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 återställer automatiskt, och useStatus rapporterar sparande, osparade ändringar och fel.
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>
</>
);
}De andra versionerna skriver samma IndexedDB-kod för hand. Deras persistens-middleware eller lagringsverktyg kan korta den med en adapter; den koden räknas inte här.
State per dokument
Två dokument har oberoende state, och att stänga det ena släpper det.
Redux Toolkit Organiserar uppdateringar i slices och actions som skickas till en store som tillhandahålls via React-kontext. Utmärkta DevTools och ett strikt, förutsägbart uppdateringsflöde för stora team.
Zustand Skapar en store-hook med create() och ändrar den med setState. Pytteliten och flexibel, med middleware för persistens och DevTools.
Jotai Sätter ihop atoms och härledda atoms, valfritt inuti en Provider. Finkorniga beroenden mellan många små bitar state.
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 skapar stores och förstör dem efter 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" />
</>
);
}En Provider eller store i Jotai och vanilla-stores i Zustand kan också isolera state; du skapar och kopplar dem själv.
Så räknas siffrorna
Icke-tomma rader i koden som visas och namn som importeras från varje state-bibliotek (React-importer räknas inte). Varje version renderar samma UI med samma lagring och samma laddnings- och feltillstånd. restate-exemplen förutsätter await restateManager.ready i appens startpunkt. Källorna har kontrollerats mot varje projekts officiella dokumentation:
- Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Välj det när en enda granskningsbar actionlogg och ett stort middleware-ekosystem väger tyngst
- Zustand — Zustand · Introduction, Zustand · persist. Välj det när du vill ha minsta möjliga kärna och hellre bygger med middleware
- Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Välj det när ditt state till sin natur är en graf av små härledda värden
02 — Snabbt
Rendera bara det som ändrats. Skriv en gång.
Båda panelerna kör det riktiga restate-paketet i din webbläsare. Klicka, skriv och titta på räknarna.
Renderingskarta
Samma fyra fält lästa på två sätt. Ändra ett fält och räkna renderingarna.
- Namn
- Tema
- Räknare
- Utkast
- Namn
- Tema
- Räknare
- Utkast
Sammanslagna skrivningar
autoSave väntar på en paus i skrivandet och skriver sedan en gång. En serie save()-anrop delar en skrivning i kön.
persistedRestate('', { key: 'note', autoSave: { delayMs: 400 } });Siffrorna kommer från riktiga React-renderingar och en lagringsadapter som räknar skrivningar på den här sidan. Andra bibliotek kan också välja värden; här är urval helt enkelt sättet att läsa.
03 — Stabilt
När lagringen fallerar ligger din data kvar.
Det är vid sparandet som state går förlorat: långsamma diskar, blockerad lagring, stängda flikar. Det här är garantier i restate 1.0, inte råd.
- 01
Sparningar sker i ordning
Varje store kör lagringsåtgärder en i taget, så en långsam sparning låter aldrig ett gammalt värde skriva över ett nyare.
save() · restore() - 02
En väntande sparning tar det senaste värdet
Att anropa save() medan en annan sparning väntar i kön ansluter till den i stället för att stapla fler skrivningar.
save(): Promise<T> - 03
Olästa poster skrivs aldrig över av misstag
Om en post inte lästes in lämnar autoSave och massparningar den orörd och rapporterar LOAD_FAILED. Bara ett explicit save() kan ersätta den.
LOAD_FAILED - 04
Även efter unmount slutförs sparningen
Sparningar som begärts före unmount slutförs. Senare skrivningar till en förstörd store ignoreras, precis som React-state efter unmount.
useRestateScope - 05
Ingen lagring? Appen startar ändå
Under serverrendering eller med blockerad lagring fortsätter stores att fungera i minnet och rapporterar att lagring inte är tillgänglig.
status().available - 06
Fel berättar vad och var
Varje restate-fel har en typad kod och sin posts nyckel, så att du kan visa rätt meddelande.
error.code · error.key
Växer med din app
Ändra deklarationen. Behåll dina komponenter.
Sparande, autosparande och validering är alternativ på samma store. Komponenten till höger ändras aldrig.
Minne
Ett delat värde i minnet.
import { restate } from '@kkwaenggwari/restate'; export const useDraft = restate('');Sparad
Återställs automatiskt; skrivs när du anropar save().
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft' });Autosparande
Skrivs en gång efter 500 ms paus i skrivandet.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft', autoSave: { delayMs: 500 }, });Validerad
Äldre poster migreras och kontrolleras före användning.
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', });
Komponent — samma i varje steg
function Editor() {
const text = useDraft();
return <textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />;
}Fungerar där React fungerar
Ett paket för varje React-miljö.
react >=16.8.0React
Fungerar med React 16.8 och senare, i webbläsaren och vid serverrendering.
react-native-mmkv >=2.0.0React Native
Samma importer. Sparande använder MMKV så fort du installerar react-native-mmkv.
SSRServerrendering
En komponents stores tar serverns ögonblicksbild från props och börjar läsa in efter hydrering.
index.d.tsTypeScript
Deklarationer ingår. Selektorer, statusfält och felkoder är typade.
Kom igång
Ditt första delade state på en minut.
Installera, deklarera, använd. Lägg till sparande när appen behöver det.
- 01Installera
npm install @kkwaenggwari/restate - 02Deklarera en store
export const useCart = restate({ items: 0, total: 0 }); - 03Läs den i vilken komponent som helst
const items = useCart((cart) => cart.items);
KKWAENGGWARI