Κατάσταση για React και React Native · 1.0
Μοιραστείτε κατάσταση.Ένα hook.Τέλος.
Δηλώστε ένα store σε μία γραμμή και χρησιμοποιήστε το σε οποιοδήποτε component, χωρίς Provider, action και reducer. Ένα component αποδίδεται ξανά μόνο όταν αλλάζει η τιμή που επέλεξε, και η αποθήκευση γίνεται όταν αποφασίσετε.
npm install @kkwaenggwari/restateΖωντανά — ένα πραγματικό store του restate
useCart.set(cart => ({ ...cart, items: cart.items + 1, total: cart.total + 12 }))Αλλάζουν τα items και το total: αποδίδονται τρεις κάρτες, η δωροκάρτα μένει ακίνητη.
- Απλό0Provider, action και reducer για τη ρύθμισηΔηλώστε μια τιμή και χρησιμοποιήστε την. Δεν χρειάζεται να συνδέσετε τίποτα εκ των προτέρων.
- Γρήγορο11 → 1Έντεκα αλλαγές, μία εγγραφήΤο autoSave περιμένει μια παύση, οπότε έντεκα γρήγορες αλλαγές γίνονται μία εγγραφή.
- Σταθερό26Κωδικοί σφάλματος με τύπουςΚάθε σφάλμα λέει τι πήγε στραβά και ποια εγγραφή αφορά.
01 — Απλό
Ίδια λειτουργία, λιγότερες έννοιες.
Τέσσερις υλοποιήσεις της ίδιας εργασίας δίπλα δίπλα. Οι αριθμοί προέρχονται από τον κώδικα που φαίνεται εδώ και μετρώνται κατά το build αυτού του ιστότοπου.
Κοινός μετρητής
Δύο components διαβάζουν και ενημερώνουν τον ίδιο αριθμό.
Redux Toolkit Οργανώνει τις ενημερώσεις σε slices και actions που αποστέλλονται σε ένα store διαθέσιμο μέσω του context του React. Εξαιρετικά DevTools και αυστηρή, προβλέψιμη ροή ενημερώσεων για μεγάλες ομάδες.
Zustand Δημιουργεί ένα hook store με create() και το αλλάζει με setState. Μικροσκοπικό και ευέλικτο, με middleware για μόνιμη αποθήκευση και DevTools.
Jotai Συνθέτει atoms και παράγωγα atoms, προαιρετικά μέσα σε Provider. Λεπτομερείς εξαρτήσεις ανάμεσα σε πολλά μικρά κομμάτια κατάστασης.
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 Μία δήλωση είναι ταυτόχρονα store και 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 />
</>
);
}Για αυτή την εργασία, και τα Zustand και Jotai είναι μικρά και χωρίς Provider. Η διαφορά μεγαλώνει όταν προστίθενται αποθήκευση και κατάσταση ανά έγγραφο.
Επιλογή πεδίου
Το component τίτλου δεν πρέπει να αποδίδεται όταν αλλάζει μόνο το κείμενο.
Redux Toolkit Οργανώνει τις ενημερώσεις σε slices και actions που αποστέλλονται σε ένα store διαθέσιμο μέσω του context του React. Εξαιρετικά DevTools και αυστηρή, προβλέψιμη ροή ενημερώσεων για μεγάλες ομάδες.
Zustand Δημιουργεί ένα hook store με create() και το αλλάζει με setState. Μικροσκοπικό και ευέλικτο, με middleware για μόνιμη αποθήκευση και DevTools.
Jotai Συνθέτει atoms και παράγωγα atoms, προαιρετικά μέσα σε Provider. Λεπτομερείς εξαρτήσεις ανάμεσα σε πολλά μικρά κομμάτια κατάστασης.
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 Δώστε έναν selector στο ίδιο hook που ήδη χρησιμοποιείτε.
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>
</>
);
}Όλα τα εργαλεία εδώ μπορούν να επιλέγουν. Το restate χρησιμοποιεί την ίδια κλήση hook αντί για ξεχωριστό hook selector ή παράγωγο atom.
Αποθήκευση πρόχειρου
Ανακτήστε ένα αποθηκευμένο πρόχειρο κατά την εκκίνηση, αποθηκεύστε με κλικ και δείξτε τις καταστάσεις αποθήκευσης σε εξέλιξη, μη αποθηκευμένο και αποτυχία.
Redux Toolkit Οργανώνει τις ενημερώσεις σε slices και actions που αποστέλλονται σε ένα store διαθέσιμο μέσω του context του React. Εξαιρετικά DevTools και αυστηρή, προβλέψιμη ροή ενημερώσεων για μεγάλες ομάδες.
Zustand Δημιουργεί ένα hook store με create() και το αλλάζει με setState. Μικροσκοπικό και ευέλικτο, με middleware για μόνιμη αποθήκευση και DevTools.
Jotai Συνθέτει atoms και παράγωγα atoms, προαιρετικά μέσα σε Provider. Λεπτομερείς εξαρτήσεις ανάμεσα σε πολλά μικρά κομμάτια κατάστασης.
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 ανακτά αυτόματα και το useStatus αναφέρει αποθήκευση, μη αποθηκευμένες αλλαγές και σφάλματα.
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>
</>
);
}Οι άλλες εκδόσεις γράφουν με το χέρι τον ίδιο κώδικα IndexedDB. Τα middleware μόνιμης αποθήκευσης ή τα εργαλεία αποθήκευσής τους μπορούν να τον συντομεύσουν με έναν προσαρμογέα· αυτός ο κώδικας δεν μετράται εδώ.
Κατάσταση ανά έγγραφο
Δύο έγγραφα κρατούν ανεξάρτητη κατάσταση, και το κλείσιμο του ενός την απελευθερώνει.
Redux Toolkit Οργανώνει τις ενημερώσεις σε slices και actions που αποστέλλονται σε ένα store διαθέσιμο μέσω του context του React. Εξαιρετικά DevTools και αυστηρή, προβλέψιμη ροή ενημερώσεων για μεγάλες ομάδες.
Zustand Δημιουργεί ένα hook store με create() και το αλλάζει με setState. Μικροσκοπικό και ευέλικτο, με middleware για μόνιμη αποθήκευση και DevTools.
Jotai Συνθέτει atoms και παράγωγα atoms, προαιρετικά μέσα σε Provider. Λεπτομερείς εξαρτήσεις ανάμεσα σε πολλά μικρά κομμάτια κατάστασης.
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 δημιουργεί stores και τα καταστρέφει μετά την αποπροσάρτηση.
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 ή ένα store στο Jotai και τα vanilla stores στο Zustand μπορούν επίσης να απομονώσουν κατάσταση· τα δημιουργείτε και τα συνδέετε εσείς.
Πώς μετρώνται αυτοί οι αριθμοί
Οι μη κενές γραμμές του κώδικα που φαίνεται και τα ονόματα που εισάγονται από κάθε βιβλιοθήκη κατάστασης (τα imports του React δεν μετρώνται). Κάθε έκδοση αποδίδει το ίδιο UI με την ίδια αποθήκευση και τις ίδιες καταστάσεις φόρτωσης και σφάλματος. Τα παραδείγματα restate υποθέτουν await restateManager.ready στο σημείο εισόδου της εφαρμογής. Οι πηγές ελέγχθηκαν με την επίσημη τεκμηρίωση κάθε έργου:
- Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Επιλέξτε το όταν μετράει περισσότερο ένα ελέγξιμο αρχείο actions και ένα μεγάλο οικοσύστημα middleware
- Zustand — Zustand · Introduction, Zustand · persist. Επιλέξτε το όταν θέλετε τον μικρότερο δυνατό πυρήνα και προτιμάτε να συνθέτετε middleware
- Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Επιλέξτε το όταν η κατάστασή σας είναι εκ φύσεως γράφος από μικρές παράγωγες τιμές
02 — Γρήγορο
Αποδώστε μόνο ό,τι άλλαξε. Γράψτε μία φορά.
Και τα δύο πάνελ τρέχουν το πραγματικό πακέτο restate στο πρόγραμμα περιήγησής σας. Κάντε κλικ, πληκτρολογήστε και παρακολουθήστε τους μετρητές.
Χάρτης αποδόσεων
Τα ίδια τέσσερα πεδία, διαβασμένα με δύο τρόπους. Αλλάξτε ένα πεδίο και μετρήστε τις αποδόσεις.
- Όνομα
- Θέμα
- Μετρητής
- Πρόχειρο
- Όνομα
- Θέμα
- Μετρητής
- Πρόχειρο
Συγχωνευμένες εγγραφές
Το autoSave περιμένει μια παύση στην πληκτρολόγηση και μετά γράφει μία φορά. Μια σειρά κλήσεων save() μοιράζεται μία εγγραφή στην ουρά.
persistedRestate('', { key: 'note', autoSave: { delayMs: 400 } });Οι αριθμοί προέρχονται από πραγματικές αποδόσεις React και από έναν προσαρμογέα αποθήκευσης που μετρά τις εγγραφές σε αυτή τη σελίδα. Και άλλες βιβλιοθήκες μπορούν να επιλέγουν τιμές· εδώ η επιλογή είναι απλώς ένας τρόπος ανάγνωσης.
03 — Σταθερό
Όταν η αποθήκευση αποτύχει, τα δεδομένα σας μένουν στη θέση τους.
Η κατάσταση χάνεται ακριβώς την ώρα της αποθήκευσης: αργοί δίσκοι, μπλοκαρισμένη αποθήκευση, κλειστές καρτέλες. Αυτές είναι εγγυήσεις του restate 1.0, όχι συμβουλές.
- 01
Οι αποθηκεύσεις εκτελούνται με σειρά
Κάθε store εκτελεί τις λειτουργίες αποθήκευσης μία μία, οπότε μια αργή αποθήκευση δεν αφήνει ποτέ μια παλιά τιμή να αντικαταστήσει μια νεότερη.
save() · restore() - 02
Μια εκκρεμής αποθήκευση παίρνει την πιο πρόσφατη τιμή
Μια κλήση save() ενώ μια άλλη αποθήκευση βρίσκεται στην ουρά ενώνεται μαζί της αντί να συσσωρεύει επιπλέον εγγραφές.
save(): Promise<T> - 03
Οι μη αναγνωσμένες εγγραφές δεν αντικαθίστανται ποτέ κατά λάθος
Αν μια εγγραφή δεν φορτώθηκε, το autoSave και οι μαζικές αποθηκεύσεις δεν την αγγίζουν και αναφέρουν LOAD_FAILED. Μόνο ένα ρητό save() μπορεί να την αντικαταστήσει.
LOAD_FAILED - 04
Η αποθήκευση ολοκληρώνεται και μετά την αποπροσάρτηση
Οι αποθηκεύσεις που ζητήθηκαν πριν από την αποπροσάρτηση ολοκληρώνονται. Οι μεταγενέστερες εγγραφές σε ένα καταστραμμένο store αγνοούνται, όπως η κατάσταση του React μετά την αποπροσάρτηση.
useRestateScope - 05
Δεν υπάρχει αποθήκευση; Η εφαρμογή ξεκινά κανονικά
Κατά την απόδοση στον διακομιστή ή όταν η αποθήκευση είναι μπλοκαρισμένη, τα stores συνεχίζουν να λειτουργούν στη μνήμη και αναφέρουν ότι η αποθήκευση δεν είναι διαθέσιμη.
status().available - 06
Τα σφάλματα λένε τι και πού
Κάθε σφάλμα του restate φέρει κωδικό με τύπο και το key της εγγραφής του, ώστε να εμφανίσετε το σωστό μήνυμα.
error.code · error.key
Μεγαλώνει μαζί με την εφαρμογή σας
Αλλάξτε τη δήλωση. Κρατήστε τα components.
Η αποθήκευση, η αυτόματη αποθήκευση και η επικύρωση είναι επιλογές του ίδιου store. Το component στα δεξιά δεν αλλάζει ποτέ.
Μνήμη
Μια κοινή τιμή στη μνήμη.
import { restate } from '@kkwaenggwari/restate'; export const useDraft = restate('');Αποθηκευμένο
Ανακτάται αυτόματα· γράφεται όταν καλείτε save().
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft' });Αυτόματη αποθήκευση
Γράφεται μία φορά μετά από παύση 500 ms στην πληκτρολόγηση.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft', autoSave: { delayMs: 500 }, });Με επικύρωση
Οι παλαιότερες εγγραφές μεταφέρονται και ελέγχονται πριν από τη χρήση.
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 — ίδιο σε κάθε βήμα
function Editor() {
const text = useDraft();
return <textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />;
}Τρέχει όπου τρέχει το React
Ένα πακέτο για κάθε περιβάλλον React.
react >=16.8.0React
Λειτουργεί με React 16.8 και νεότερο, στο πρόγραμμα περιήγησης και κατά την απόδοση στον διακομιστή.
react-native-mmkv >=2.0.0React Native
Τα ίδια imports. Η αποθήκευση χρησιμοποιεί MMKV μόλις εγκαταστήσετε το react-native-mmkv.
SSRΑπόδοση στον διακομιστή
Τα stores των components παίρνουν το στιγμιότυπο του διακομιστή από τα props και αρχίζουν να φορτώνουν μετά το hydration.
index.d.tsTypeScript
Οι δηλώσεις περιλαμβάνονται. Οι selectors, τα πεδία κατάστασης και οι κωδικοί σφάλματος έχουν τύπους.
Πρώτα βήματα
Η πρώτη σας κοινή κατάσταση σε ένα λεπτό.
Εγκαταστήστε, δηλώστε, χρησιμοποιήστε. Προσθέστε αποθήκευση όταν τη χρειαστεί η εφαρμογή.
- 01Εγκατάσταση
npm install @kkwaenggwari/restate - 02Δηλώστε ένα store
export const useCart = restate({ items: 0, total: 0 }); - 03Διαβάστε το σε οποιοδήποτε component
const items = useCart((cart) => cart.items);
KKWAENGGWARI