Tila Reactille ja React Nativelle · 1.0
Jaa tila.Yksi hook.Valmis.
Määrittele store yhdellä rivillä ja käytä sitä missä tahansa komponentissa ilman Provideria, actioneita ja reducereita. Komponentti renderöityy uudelleen vain, kun sen valitsema arvo muuttuu, ja tallennus tapahtuu, kun päätät.
npm install @kkwaenggwari/restateLive — oikea restate-store
useCart.set(cart => ({ ...cart, items: cart.items + 1, total: cart.total + 12 }))items ja total muuttuvat: kolme korttia renderöityy, lahjakortti pysyy paikallaan.
- Yksinkertainen0Provider, actionit ja reducerit alustukseenMäärittele arvo ja käytä sitä. Mitään ei tarvitse kytkeä etukäteen.
- Nopea11 → 1Yksitoista muutosta, yksi kirjoitusautoSave odottaa taukoa, joten yksitoista nopeaa muutosta muuttuu yhdeksi kirjoitukseksi.
- Vakaa26Tyypitetyt virhekooditJokainen virhe kertoo, mikä meni pieleen ja mitä tietuetta se koskee.
01 — Yksinkertainen
Sama toiminto, vähemmän käsitteitä.
Saman tehtävän neljä toteutusta rinnakkain. Luvut tulevat tässä näytetystä koodista, ja ne mitataan tämän sivuston koonnin yhteydessä.
Jaettu laskuri
Kaksi komponenttia lukee ja päivittää samaa lukua.
Redux Toolkit Järjestää päivitykset sliceiksi ja actioneiksi, jotka lähetetään React-kontekstin kautta tarjottuun storeen. Erinomaiset DevTools-työkalut ja tiukka, ennustettava päivityskulku suurille tiimeille.
Zustand Luo store-hookin create()-funktiolla ja muuttaa sitä setStatella. Pienenpieni ja joustava, mukana middlewaret pysyvyyteen ja DevTools-työkaluihin.
Jotai Kokoaa atomeja ja johdettuja atomeja, halutessa Providerin sisällä. Hienojakoiset riippuvuudet monien pienten tilapalojen välillä.
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 Yksi määrittely on sekä store että 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 />
</>
);
}Tähän tehtävään myös Zustand ja Jotai ovat pieniä ja toimivat ilman Provideria. Ero kasvaa, kun mukaan tulevat tallennus ja dokumenttikohtainen tila.
Kentän valitseminen
Otsikkokomponentin ei pitäisi renderöityä, kun vain teksti muuttuu.
Redux Toolkit Järjestää päivitykset sliceiksi ja actioneiksi, jotka lähetetään React-kontekstin kautta tarjottuun storeen. Erinomaiset DevTools-työkalut ja tiukka, ennustettava päivityskulku suurille tiimeille.
Zustand Luo store-hookin create()-funktiolla ja muuttaa sitä setStatella. Pienenpieni ja joustava, mukana middlewaret pysyvyyteen ja DevTools-työkaluihin.
Jotai Kokoaa atomeja ja johdettuja atomeja, halutessa Providerin sisällä. Hienojakoiset riippuvuudet monien pienten tilapalojen välillä.
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 Anna selector samalle hookille, jota jo käytät.
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>
</>
);
}Kaikki tässä esitellyt työkalut osaavat valita. restate käyttää samaa hook-kutsua erillisen selector-hookin tai johdetun atomin sijaan.
Luonnoksen tallentaminen
Palauta tallennettu luonnos käynnistyksessä, tallenna napsautuksella ja näytä tallennetaan-, tallentamaton- ja epäonnistui-tilat.
Redux Toolkit Järjestää päivitykset sliceiksi ja actioneiksi, jotka lähetetään React-kontekstin kautta tarjottuun storeen. Erinomaiset DevTools-työkalut ja tiukka, ennustettava päivityskulku suurille tiimeille.
Zustand Luo store-hookin create()-funktiolla ja muuttaa sitä setStatella. Pienenpieni ja joustava, mukana middlewaret pysyvyyteen ja DevTools-työkaluihin.
Jotai Kokoaa atomeja ja johdettuja atomeja, halutessa Providerin sisällä. Hienojakoiset riippuvuudet monien pienten tilapalojen välillä.
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 palauttaa automaattisesti, ja useStatus raportoi tallennuksen, tallentamattomat muutokset ja virheet.
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>
</>
);
}Muut versiot kirjoittavat saman IndexedDB-koodin käsin. Niiden pysyvyysmiddlewaret tai tallennustyökalut voivat lyhentää sitä sovittimella; sitä koodia ei lasketa tässä.
Dokumenttikohtainen tila
Kaksi dokumenttia pitää itsenäistä tilaa, ja toisen sulkeminen vapauttaa sen.
Redux Toolkit Järjestää päivitykset sliceiksi ja actioneiksi, jotka lähetetään React-kontekstin kautta tarjottuun storeen. Erinomaiset DevTools-työkalut ja tiukka, ennustettava päivityskulku suurille tiimeille.
Zustand Luo store-hookin create()-funktiolla ja muuttaa sitä setStatella. Pienenpieni ja joustava, mukana middlewaret pysyvyyteen ja DevTools-työkaluihin.
Jotai Kokoaa atomeja ja johdettuja atomeja, halutessa Providerin sisällä. Hienojakoiset riippuvuudet monien pienten tilapalojen välillä.
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 luo storet ja tuhoaa ne irrottamisen jälkeen.
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" />
</>
);
}Jotain Provider tai store ja Zustandin vanilla-storet voivat myös eristää tilan; luot ja kytket ne itse.
Miten nämä luvut lasketaan
Näytetyn koodin ei-tyhjät rivit ja kustakin tilakirjastosta importatut nimet (React-importteja ei lasketa). Jokainen versio renderöi saman UI:n samalla tallennustilalla sekä samoilla lataus- ja virhetiloilla. restate-esimerkit olettavat await restateManager.ready -kutsun sovelluksen aloituspisteessä. Lähteet on tarkistettu kunkin projektin virallisesta dokumentaatiosta:
- Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Valitse se, kun tärkeintä on tarkastettava action-loki ja laaja middleware-ekosysteemi
- Zustand — Zustand · Introduction, Zustand · persist. Valitse se, kun haluat pienimmän ytimen ja kokoat mieluummin middlewaret itse
- Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Valitse se, kun tilasi on luonteeltaan pienten johdettujen arvojen verkko
02 — Nopea
Renderöi vain muuttunut. Kirjoita kerran.
Molemmat paneelit ajavat oikeaa restate-pakettia selaimessasi. Napsauta, kirjoita ja seuraa laskureita.
Renderöintikartta
Samat neljä kenttää luettuna kahdella tavalla. Muuta kenttää ja laske renderöinnit.
- Nimi
- Teema
- Laskuri
- Luonnos
- Nimi
- Teema
- Laskuri
- Luonnos
Yhdistetyt kirjoitukset
autoSave odottaa taukoa kirjoittamisessa ja kirjoittaa sitten kerran. Peräkkäiset save()-kutsut jakavat yhden jonossa olevan kirjoituksen.
persistedRestate('', { key: 'note', autoSave: { delayMs: 400 } });Luvut tulevat oikeista React-renderöinneistä ja tallennussovittimesta, joka laskee kirjoitukset tällä sivulla. Muutkin kirjastot osaavat valita arvoja; tässä valitseminen on vain tapa lukea.
03 — Vakaa
Kun tallennus pettää, tietosi pysyvät paikallaan.
Tila katoaa juuri tallennettaessa: hitaat levyt, estetty tallennustila, suljetut välilehdet. Nämä ovat restate 1.0:n takuita, eivät neuvoja.
- 01
Tallennukset suoritetaan järjestyksessä
Jokainen store suorittaa tallennustoiminnot yksi kerrallaan, joten hidas tallennus ei koskaan anna vanhan arvon korvata uudempaa.
save() · restore() - 02
Odottava tallennus ottaa uusimman arvon
Kun kutsut save()-funktiota toisen tallennuksen ollessa jonossa, se liittyy siihen sen sijaan, että kasaisi ylimääräisiä kirjoituksia.
save(): Promise<T> - 03
Lukemattomia tietueita ei koskaan korvata vahingossa
Jos tietue ei latautunut, autoSave ja joukkotallennukset eivät koske siihen vaan raportoivat LOAD_FAILED. Vain nimenomainen save() voi korvata sen.
LOAD_FAILED - 04
Tallennus valmistuu irrottamisen jälkeenkin
Ennen irrottamista pyydetyt tallennukset valmistuvat. Myöhemmät kirjoitukset tuhottuun storeen ohitetaan, kuten Reactin tila irrottamisen jälkeen.
useRestateScope - 05
Ei tallennustilaa? Sovellus käynnistyy silti
Palvelinrenderöinnin aikana tai kun tallennustila on estetty, storet toimivat edelleen muistissa ja ilmoittavat, ettei tallennustila ole käytettävissä.
status().available - 06
Virheet kertovat mitä ja missä
Jokaisessa restate-virheessä on tyypitetty koodi ja tietueen key, jotta voit näyttää oikean viestin.
error.code · error.key
Kasvaa sovelluksesi mukana
Muuta määrittelyä. Pidä komponentit.
Tallennus, automaattinen tallennus ja validointi ovat saman storen asetuksia. Oikealla oleva komponentti ei muutu koskaan.
Muisti
Jaettu arvo muistissa.
import { restate } from '@kkwaenggwari/restate'; export const useDraft = restate('');Tallennettu
Palautuu automaattisesti; kirjoitetaan, kun kutsut save()-funktiota.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft' });Automaattitallennus
Kirjoitetaan kerran 500 ms:n kirjoitustauon jälkeen.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft', autoSave: { delayMs: 500 }, });Validoitu
Vanhemmat tietueet siirretään ja tarkistetaan ennen käyttöä.
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', });
Komponentti — sama jokaisessa vaiheessa
function Editor() {
const text = useDraft();
return <textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />;
}Toimii kaikkialla, missä React toimii
Yksi paketti jokaiseen React-ympäristöön.
react >=16.8.0React
Toimii React 16.8:n ja uudempien kanssa selaimessa ja palvelinrenderöinnin aikana.
react-native-mmkv >=2.0.0React Native
Samat importit. Tallennus käyttää MMKV:tä heti, kun asennat react-native-mmkv-paketin.
SSRPalvelinrenderöinti
Komponenttien storet ottavat palvelimen tilannekuvan propseista ja aloittavat latauksen hydraation jälkeen.
index.d.tsTypeScript
Tyyppimäärittelyt sisältyvät. Selectorit, tilakentät ja virhekoodit on tyypitetty.
Aloittaminen
Ensimmäinen jaettu tilasi minuutissa.
Asenna, määrittele, käytä. Lisää tallennus, kun sovellus tarvitsee sitä.
- 01Asennus
npm install @kkwaenggwari/restate - 02Määrittele store
export const useCart = restate({ items: 0, total: 0 }); - 03Lue sitä missä tahansa komponentissa
const items = useCart((cart) => cart.items);
KKWAENGGWARI