State cho React và React Native · 1.0
Chia sẻ state.Một hook.Xong.
Khai báo store trong một dòng và dùng nó trong bất kỳ component nào, không cần Provider, action và reducer. Component chỉ render lại khi giá trị nó chọn thay đổi, và việc lưu diễn ra khi bạn quyết định.
npm install @kkwaenggwari/restateTrực tiếp — một store restate thật
useCart.set(cart => ({ ...cart, items: cart.items + 1, total: cart.total + 12 }))items và total thay đổi: ba thẻ render, thẻ quà tặng đứng yên.
- Đơn giản0Provider, action và reducer để thiết lậpKhai báo giá trị và dùng nó. Không có gì phải nối trước.
- Nhanh11 → 1Mười một thay đổi, một lần ghiautoSave chờ một khoảng dừng, nên mười một thay đổi nhanh trở thành một lần ghi.
- Ổn định26Mã lỗi có kiểuMỗi lỗi cho biết điều gì sai và bản ghi nào liên quan.
01 — Đơn giản
Cùng tính năng, ít khái niệm hơn.
Bốn cách hiện thực cùng một việc, đặt cạnh nhau. Các con số lấy từ mã hiển thị ở đây và được đo khi build trang này.
Bộ đếm dùng chung
Hai component đọc và cập nhật cùng một con số.
Redux Toolkit Tổ chức cập nhật thành slice và action được gửi đến một store cung cấp qua context của React. DevTools xuất sắc và luồng cập nhật chặt chẽ, dễ đoán cho các nhóm lớn.
Zustand Tạo hook store bằng create() và thay đổi nó bằng setState. Rất nhỏ và linh hoạt, có middleware cho lưu trữ bền vững và DevTools.
Jotai Kết hợp atom và atom dẫn xuất, có thể đặt trong Provider. Phụ thuộc chi tiết giữa nhiều mảnh state nhỏ.
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 Một khai báo vừa là store vừa là 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 />
</>
);
}Với tác vụ này, Zustand và Jotai cũng nhỏ gọn và không cần Provider. Khoảng cách lớn dần khi thêm việc lưu và state theo từng tài liệu.
Chọn một trường
Component tiêu đề không nên render khi chỉ phần văn bản thay đổi.
Redux Toolkit Tổ chức cập nhật thành slice và action được gửi đến một store cung cấp qua context của React. DevTools xuất sắc và luồng cập nhật chặt chẽ, dễ đoán cho các nhóm lớn.
Zustand Tạo hook store bằng create() và thay đổi nó bằng setState. Rất nhỏ và linh hoạt, có middleware cho lưu trữ bền vững và DevTools.
Jotai Kết hợp atom và atom dẫn xuất, có thể đặt trong Provider. Phụ thuộc chi tiết giữa nhiều mảnh state nhỏ.
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 Truyền selector vào chính hook bạn đang dùng.
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>
</>
);
}Mọi công cụ ở đây đều chọn được giá trị. restate dùng cùng lời gọi hook thay vì một hook selector riêng hay atom dẫn xuất.
Lưu bản nháp
Khôi phục bản nháp đã lưu khi khởi động, lưu khi nhấp, và hiển thị trạng thái đang lưu, chưa lưu và thất bại.
Redux Toolkit Tổ chức cập nhật thành slice và action được gửi đến một store cung cấp qua context của React. DevTools xuất sắc và luồng cập nhật chặt chẽ, dễ đoán cho các nhóm lớn.
Zustand Tạo hook store bằng create() và thay đổi nó bằng setState. Rất nhỏ và linh hoạt, có middleware cho lưu trữ bền vững và DevTools.
Jotai Kết hợp atom và atom dẫn xuất, có thể đặt trong Provider. Phụ thuộc chi tiết giữa nhiều mảnh state nhỏ.
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 tự khôi phục, còn useStatus báo việc lưu, thay đổi chưa lưu và lỗi.
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>
</>
);
}Các phiên bản khác viết tay cùng đoạn mã IndexedDB. Middleware lưu trữ bền vững hoặc công cụ lưu trữ của họ có thể rút gọn bằng một adapter; đoạn mã đó không được tính ở đây.
State theo từng tài liệu
Hai tài liệu giữ state riêng, và đóng một tài liệu sẽ giải phóng state của nó.
Redux Toolkit Tổ chức cập nhật thành slice và action được gửi đến một store cung cấp qua context của React. DevTools xuất sắc và luồng cập nhật chặt chẽ, dễ đoán cho các nhóm lớn.
Zustand Tạo hook store bằng create() và thay đổi nó bằng setState. Rất nhỏ và linh hoạt, có middleware cho lưu trữ bền vững và DevTools.
Jotai Kết hợp atom và atom dẫn xuất, có thể đặt trong Provider. Phụ thuộc chi tiết giữa nhiều mảnh state nhỏ.
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 tạo store và hủy chúng sau khi 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 hoặc store trong Jotai và vanilla store trong Zustand cũng có thể tách state; bạn tự tạo và nối chúng.
Cách đếm các con số này
Các dòng không trống của đoạn mã hiển thị và các tên được import từ mỗi thư viện state (không tính import của React). Mỗi phiên bản render cùng UI với cùng bộ nhớ lưu trữ, cùng trạng thái tải và lỗi. Ví dụ restate giả định await restateManager.ready ở điểm vào ứng dụng. Nguồn đã được đối chiếu với tài liệu chính thức của từng dự án:
- Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Chọn khi một nhật ký action có thể kiểm toán và hệ sinh thái middleware lớn là quan trọng nhất
- Zustand — Zustand · Introduction, Zustand · persist. Chọn khi bạn muốn lõi nhỏ nhất có thể và thích tự ghép middleware
- Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Chọn khi state của bạn vốn là một đồ thị gồm các giá trị dẫn xuất nhỏ
02 — Nhanh
Chỉ render phần thay đổi. Ghi một lần.
Cả hai bảng đều chạy gói restate thật trong trình duyệt của bạn. Hãy nhấp, gõ và theo dõi bộ đếm.
Bản đồ render
Cùng bốn trường, đọc theo hai cách. Thay đổi một trường và đếm số lần render.
- Tên
- Giao diện
- Bộ đếm
- Bản nháp
- Tên
- Giao diện
- Bộ đếm
- Bản nháp
Ghi gộp
autoSave chờ người dùng ngừng gõ rồi ghi một lần. Một loạt lời gọi save() dùng chung một lần ghi trong hàng đợi.
persistedRestate('', { key: 'note', autoSave: { delayMs: 400 } });Các con số đến từ những lần render React thật và một adapter lưu trữ đếm số lần ghi trên trang này. Các thư viện khác cũng có thể chọn giá trị; ở đây chọn chỉ là cách đọc.
03 — Ổn định
Khi bộ nhớ lưu trữ hỏng, dữ liệu của bạn vẫn nguyên chỗ.
State bị mất đúng lúc lưu: ổ đĩa chậm, bộ nhớ lưu trữ bị chặn, tab bị đóng. Đây là những bảo đảm của restate 1.0, không phải lời khuyên.
- 01
Các lần lưu chạy tuần tự
Mỗi store thực hiện thao tác lưu trữ lần lượt từng cái, nên một lần lưu chậm không bao giờ khiến giá trị cũ ghi đè giá trị mới hơn.
save() · restore() - 02
Lần lưu đang chờ lấy giá trị mới nhất
Gọi save() khi một lần lưu khác đang xếp hàng sẽ nhập vào lần đó thay vì chồng thêm lần ghi.
save(): Promise<T> - 03
Bản ghi chưa đọc không bao giờ bị ghi đè nhầm
Nếu một bản ghi không tải được, autoSave và các lần lưu hàng loạt sẽ không động vào nó và báo LOAD_FAILED. Chỉ save() tường minh mới thay thế được nó.
LOAD_FAILED - 04
Lưu vẫn hoàn tất sau khi unmount
Lần lưu được yêu cầu trước khi unmount sẽ hoàn tất. Những lần ghi sau đó vào store đã hủy bị bỏ qua, giống state React sau khi unmount.
useRestateScope - 05
Không có bộ nhớ lưu trữ? Ứng dụng vẫn khởi động
Khi render phía máy chủ hoặc khi bộ nhớ lưu trữ bị chặn, store vẫn chạy trong bộ nhớ và báo rằng bộ nhớ lưu trữ không khả dụng.
status().available - 06
Lỗi cho biết cái gì và ở đâu
Mỗi lỗi restate mang một mã có kiểu và key của bản ghi, để bạn hiển thị đúng thông báo.
error.code · error.key
Lớn lên cùng ứng dụng của bạn
Đổi phần khai báo. Giữ nguyên component.
Lưu, tự động lưu và kiểm tra hợp lệ là các tùy chọn của cùng một store. Component bên phải không bao giờ thay đổi.
Bộ nhớ
Giá trị dùng chung trong bộ nhớ.
import { restate } from '@kkwaenggwari/restate'; export const useDraft = restate('');Được lưu
Tự động khôi phục; ghi khi bạn gọi save().
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft' });Tự động lưu
Ghi một lần sau 500 ms ngừng gõ.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft', autoSave: { delayMs: 500 }, });Có kiểm tra hợp lệ
Bản ghi cũ được di chuyển và kiểm tra trước khi dùng.
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 — giống nhau ở mọi bước
function Editor() {
const text = useDraft();
return <textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />;
}Chạy ở mọi nơi React chạy
Một gói cho mọi môi trường React.
react >=16.8.0React
Hoạt động với React 16.8 trở lên, trong trình duyệt và khi render phía máy chủ.
react-native-mmkv >=2.0.0React Native
Cùng import. Việc lưu dùng MMKV ngay khi bạn cài react-native-mmkv.
SSRRender phía máy chủ
Store của component lấy snapshot máy chủ từ props và bắt đầu tải sau khi hydrate.
index.d.tsTypeScript
Có sẵn khai báo. Selector, các trường trạng thái và mã lỗi đều có kiểu.
Bắt đầu
State dùng chung đầu tiên của bạn trong một phút.
Cài đặt, khai báo, sử dụng. Thêm việc lưu khi ứng dụng cần.
- 01Cài đặt
npm install @kkwaenggwari/restate - 02Khai báo store
export const useCart = restate({ items: 0, total: 0 }); - 03Đọc nó trong bất kỳ component nào
const items = useCart((cart) => cart.items);
KKWAENGGWARI