State สำหรับ React และ React Native · 1.0
แชร์ statehook เดียวจบ
ประกาศ store ในบรรทัดเดียวแล้วใช้ในคอมโพเนนต์ใดก็ได้ ไม่ต้องมี Provider, action และ reducer คอมโพเนนต์จะเรนเดอร์ใหม่เมื่อค่าที่เลือกเปลี่ยนเท่านั้น และการบันทึกจะเกิดขึ้นเมื่อคุณตัดสินใจ
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 — ง่าย
ความสามารถเท่าเดิม แนวคิดน้อยลง
การเขียนงานเดียวกันสี่แบบวางเทียบกัน ตัวเลขมาจากโค้ดที่แสดงที่นี่และวัดตอนบิลด์เว็บไซต์นี้
ตัวนับที่ใช้ร่วมกัน
สองคอมโพเนนต์อ่านและอัปเดตตัวเลขเดียวกัน
Redux Toolkit จัดการอัปเดตเป็น slice และ action ที่ส่งไปยัง store ที่ให้มาผ่าน context ของ React DevTools ยอดเยี่ยมและลำดับการอัปเดตที่เข้มงวดคาดเดาได้สำหรับทีมขนาดใหญ่
Zustand สร้าง hook ของ store ด้วย create() และเปลี่ยนค่าด้วย setState เล็กมากและยืดหยุ่น มี middleware สำหรับการเก็บข้อมูลถาวรและ DevTools
Jotai ประกอบ atom และ atom ที่สืบทอดค่า จะอยู่ภายใน Provider ก็ได้ การพึ่งพากันอย่างละเอียดระหว่าง 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 การประกาศครั้งเดียวเป็นทั้ง 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 เช่นกัน ความต่างจะเพิ่มขึ้นเมื่อเพิ่มการบันทึกและ state ต่อเอกสาร
การเลือกฟิลด์
คอมโพเนนต์หัวเรื่องไม่ควรเรนเดอร์เมื่อมีแค่ข้อความที่เปลี่ยน
Redux Toolkit จัดการอัปเดตเป็น slice และ action ที่ส่งไปยัง store ที่ให้มาผ่าน context ของ React DevTools ยอดเยี่ยมและลำดับการอัปเดตที่เข้มงวดคาดเดาได้สำหรับทีมขนาดใหญ่
Zustand สร้าง hook ของ store ด้วย create() และเปลี่ยนค่าด้วย setState เล็กมากและยืดหยุ่น มี middleware สำหรับการเก็บข้อมูลถาวรและ DevTools
Jotai ประกอบ atom และ atom ที่สืบทอดค่า จะอยู่ภายใน Provider ก็ได้ การพึ่งพากันอย่างละเอียดระหว่าง 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 ส่ง 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 จัดการอัปเดตเป็น slice และ action ที่ส่งไปยัง store ที่ให้มาผ่าน context ของ React DevTools ยอดเยี่ยมและลำดับการอัปเดตที่เข้มงวดคาดเดาได้สำหรับทีมขนาดใหญ่
Zustand สร้าง hook ของ store ด้วย create() และเปลี่ยนค่าด้วย setState เล็กมากและยืดหยุ่น มี middleware สำหรับการเก็บข้อมูลถาวรและ DevTools
Jotai ประกอบ atom และ atom ที่สืบทอดค่า จะอยู่ภายใน Provider ก็ได้ การพึ่งพากันอย่างละเอียดระหว่าง 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 กู้คืนอัตโนมัติ และ 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 สำหรับเก็บข้อมูลถาวรหรือเครื่องมือจัดเก็บของไลบรารีเหล่านั้นทำให้สั้นลงได้ด้วยอะแดปเตอร์ แต่โค้ดส่วนนั้นไม่นับที่นี่
State ต่อเอกสาร
สองเอกสารเก็บ state แยกกัน และการปิดเอกสารหนึ่งจะปล่อย state ของมัน
Redux Toolkit จัดการอัปเดตเป็น slice และ action ที่ส่งไปยัง store ที่ให้มาผ่าน context ของ React DevTools ยอดเยี่ยมและลำดับการอัปเดตที่เข้มงวดคาดเดาได้สำหรับทีมขนาดใหญ่
Zustand สร้าง hook ของ store ด้วย create() และเปลี่ยนค่าด้วย setState เล็กมากและยืดหยุ่น มี middleware สำหรับการเก็บข้อมูลถาวรและ DevTools
Jotai ประกอบ atom และ atom ที่สืบทอดค่า จะอยู่ภายใน Provider ก็ได้ การพึ่งพากันอย่างละเอียดระหว่าง 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 สร้าง store และทำลายมันหลัง unmount
import { useState } from 'react';
import { restate, useRestateScope } from '@kkwaenggwari/restate';
function Document({ name }) {
const doc = useRestateScope((scope) => ({
useDraft: restate('', { scope }),
useSelection: restate(0, { scope }),
}));
const text = doc.useDraft();
const selection = doc.useSelection();
return (
<section>
<label>
{name}
<input value={text} onChange={(event) => doc.useDraft.set(event.target.value)} />
</label>
<output>{text}</output>
<button onClick={() => doc.useSelection.set((n) => n + 1)}>Selection: {selection}</button>
</section>
);
}
export default function App() {
const [leftOpen, setLeftOpen] = useState(true);
return (
<>
<button onClick={() => setLeftOpen((open) => !open)}>
{leftOpen ? 'Close left' : 'Open left'}
</button>
{leftOpen && <Document name="Left" />}
<Document name="Right" />
</>
);
}Provider หรือ store ใน Jotai และ vanilla store ใน Zustand ก็แยก state ได้เช่นกัน คุณสร้างและเชื่อมต่อมันเอง
ตัวเลขเหล่านี้นับอย่างไร
บรรทัดที่ไม่ว่างของโค้ดที่แสดงและชื่อที่ import จากไลบรารี state แต่ละตัว (ไม่นับ import ของ React) ทุกเวอร์ชันเรนเดอร์ UI เดียวกันด้วยที่จัดเก็บข้อมูลเดียวกัน และสถานะการโหลดและข้อผิดพลาดเดียวกัน ตัวอย่างของ restate ถือว่ามี await restateManager.ready อยู่ที่จุดเริ่มต้นของแอป แหล่งที่มาตรวจสอบกับเอกสารทางการของแต่ละโปรเจกต์แล้ว:
- Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. เลือกเมื่อบันทึก action ที่ตรวจสอบย้อนหลังได้และระบบนิเวศ middleware ขนาดใหญ่สำคัญที่สุด
- Zustand — Zustand · Introduction, Zustand · persist. เลือกเมื่อคุณต้องการแกนที่เล็กที่สุดและชอบประกอบ middleware เอง
- Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. เลือกเมื่อ state ของคุณโดยธรรมชาติเป็นกราฟของค่าที่สืบทอดขนาดเล็ก
02 — เร็ว
เรนเดอร์เฉพาะที่เปลี่ยน เขียนครั้งเดียว
ทั้งสองแผงรันแพ็กเกจ restate จริงในเบราว์เซอร์ของคุณ ลองคลิก พิมพ์ และดูตัวนับ
แผนที่การเรนเดอร์
สี่ฟิลด์เดิม อ่านสองแบบ เปลี่ยนฟิลด์หนึ่งแล้วนับการเรนเดอร์
- ชื่อ
- ธีม
- ตัวนับ
- ฉบับร่าง
- ชื่อ
- ธีม
- ตัวนับ
- ฉบับร่าง
การเขียนแบบรวม
autoSave รอให้หยุดพิมพ์แล้วเขียนครั้งเดียว การเรียก save() ติดกันหลายครั้งใช้การเขียนที่รอคิวร่วมกันครั้งเดียว
persistedRestate('', { key: 'note', autoSave: { delayMs: 400 } });ตัวเลขมาจากการเรนเดอร์ React จริงและอะแดปเตอร์ที่จัดเก็บข้อมูลที่นับการเขียนบนหน้านี้ ไลบรารีอื่นก็เลือกค่าได้เช่นกัน ที่นี่การเลือกเป็นเพียงวิธีอ่าน
03 — เสถียร
เมื่อที่จัดเก็บข้อมูลล้มเหลว ข้อมูลของคุณยังอยู่ที่เดิม
state หายไปตอนบันทึกพอดี: ดิสก์ช้า ที่จัดเก็บถูกบล็อก แท็บถูกปิด นี่คือสิ่งที่ restate 1.0 รับประกัน ไม่ใช่คำแนะนำ
- 01
การบันทึกทำตามลำดับ
แต่ละ store ทำงานกับที่จัดเก็บข้อมูลทีละอย่าง การบันทึกที่ช้าจึงไม่มีทางทำให้ค่าเก่าเขียนทับค่าที่ใหม่กว่า
save() · restore() - 02
การบันทึกที่รออยู่จะใช้ค่าล่าสุด
การเรียก save() ขณะที่การบันทึกอื่นรอคิวอยู่จะรวมเข้ากับการบันทึกนั้น แทนที่จะสะสมการเขียนเพิ่ม
save(): Promise<T> - 03
ระเบียนที่ยังไม่ได้อ่านจะไม่ถูกแทนที่โดยไม่ตั้งใจ
หากโหลดระเบียนไม่สำเร็จ autoSave และการบันทึกแบบกลุ่มจะไม่แตะต้องมันและรายงาน LOAD_FAILED มีเพียง save() ที่เรียกโดยตรงเท่านั้นที่แทนที่ได้
LOAD_FAILED - 04
แม้ unmount แล้ว การบันทึกก็ยังทำจนเสร็จ
การบันทึกที่ขอไว้ก่อน unmount จะทำจนเสร็จ การเขียนหลังจากนั้นลงใน store ที่ถูกทำลายแล้วจะถูกเพิกเฉย เหมือน state ของ React หลัง unmount
useRestateScope - 05
ไม่มีที่จัดเก็บข้อมูล? แอปก็ยังเริ่มทำงาน
ระหว่างการเรนเดอร์ฝั่งเซิร์ฟเวอร์หรือเมื่อที่จัดเก็บข้อมูลถูกบล็อก store จะทำงานต่อในหน่วยความจำและรายงานว่าที่จัดเก็บข้อมูลใช้ไม่ได้
status().available - 06
ข้อผิดพลาดบอกว่าอะไรและที่ไหน
ข้อผิดพลาดของ restate ทุกตัวมีรหัสแบบมีชนิดและ key ของระเบียน เพื่อให้คุณแสดงข้อความที่ถูกต้องได้
error.code · error.key
เติบโตไปพร้อมกับแอปของคุณ
เปลี่ยนการประกาศ คอมโพเนนต์คงเดิม
การบันทึก การบันทึกอัตโนมัติ และการตรวจสอบความถูกต้องเป็นตัวเลือกของ store เดียวกัน คอมโพเนนต์ทางขวาไม่เคยเปลี่ยน
หน่วยความจำ
ค่าที่ใช้ร่วมกันในหน่วยความจำ
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', });
คอมโพเนนต์ — เหมือนเดิมทุกขั้น
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
import เดียวกัน การบันทึกจะใช้ MMKV ทันทีที่คุณติดตั้ง react-native-mmkv
SSRการเรนเดอร์ฝั่งเซิร์ฟเวอร์
store ของคอมโพเนนต์รับสแนปช็อตจากเซิร์ฟเวอร์ผ่าน props และเริ่มโหลดหลัง hydration
index.d.tsTypeScript
มีการประกาศชนิดมาให้ selector ฟิลด์สถานะ และรหัสข้อผิดพลาดล้วนมีชนิด
เริ่มต้นใช้งาน
state ที่ใช้ร่วมกันตัวแรกของคุณภายในหนึ่งนาที
ติดตั้ง ประกาศ แล้วใช้ เพิ่มการบันทึกเมื่อแอปต้องการ
- 01ติดตั้ง
npm install @kkwaenggwari/restate - 02ประกาศ store
export const useCart = restate({ items: 0, total: 0 }); - 03อ่านในคอมโพเนนต์ใดก็ได้
const items = useCart((cart) => cart.items);
KKWAENGGWARI