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.

Terminal
npm install @kkwaenggwari/restate

Trự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.

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ỏ.

Redux Toolkit29 dòng · 5 API
JavaScript / JSX
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>
  );
}
Zustand16 dòng · 1 API
JavaScript / JSX
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 />
    </>
  );
}
Jotai13 dòng · 2 API
JavaScript / JSX
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.

restate13 dòng · 1 API
JavaScript / JSX
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ỏ.

Redux Toolkit26 dòng · 4 API
JavaScript / JSX
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>
  );
}
Zustand17 dòng · 1 API
JavaScript / JSX
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>
    </>
  );
}
Jotai19 dòng · 3 API
JavaScript / JSX
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.

restate17 dòng · 1 API
JavaScript / JSX
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ỏ.

Redux Toolkit84 dòng · 4 API
JavaScript / JSX
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>
  );
}
Zustand79 dòng · 1 API
JavaScript / JSX
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 />;
}
Jotai80 dòng · 3 API
JavaScript / JSX
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.

restate18 dòng · 1 API
JavaScript / JSX
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ỏ.

Redux Toolkit42 dòng · 4 API
JavaScript / JSX
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" />
    </>
  );
}
Zustand35 dòng · 2 API
JavaScript / JSX
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" />
    </>
  );
}
Jotai33 dòng · 3 API
JavaScript / JSX
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.

restate27 dòng · 2 API
JavaScript / JSX
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.

Đọc toàn bộ store
  • Tên
  • Giao diện
  • Bộ đếm
  • Bản nháp
Chọn một trường
  • 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.

Mở phòng thử lỗi Đọc hướng dẫn về độ tin cậy
  1. 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()
  2. 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>
  3. 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
  4. 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
  5. 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
  6. 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.

  1. Bộ nhớ

    Giá trị dùng chung trong bộ nhớ.

    import { restate } from '@kkwaenggwari/restate';
    
    export const useDraft = restate('');
  2. Đượ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' });
  3. 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 },
    });
  4. 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.0

    React

    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.0

    React Native

    Cùng import. Việc lưu dùng MMKV ngay khi bạn cài react-native-mmkv.

  • SSR

    Render 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.ts

    TypeScript

    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.

  1. 01
    Cài đặt
    Terminal
    npm install @kkwaenggwari/restate
  2. 02
    Khai báo store
    JavaScript / JSX
    export const useCart = restate({ items: 0, total: 0 });
  3. 03
    Đọc nó trong bất kỳ component nào
    JavaScript / JSX
    const items = useCart((cart) => cart.items);

KKWAENGGWARI

Dự án

Tìm trong tài liệu

Tìm trong tiêu đề, mô tả và nội dung của mọi hướng dẫn.