State สำหรับ React และ React Native · 1.0

แชร์ statehook เดียวจบ

ประกาศ store ในบรรทัดเดียวแล้วใช้ในคอมโพเนนต์ใดก็ได้ ไม่ต้องมี Provider, action และ reducer คอมโพเนนต์จะเรนเดอร์ใหม่เมื่อค่าที่เลือกเปลี่ยนเท่านั้น และการบันทึกจะเกิดขึ้นเมื่อคุณตัดสินใจ

Terminal
npm install @kkwaenggwari/restate

สด — store ของ restate จริง

useCart.set(cart => ({ ...cart, items: cart.items + 1, total: cart.total + 12 }))

items และ total เปลี่ยน: สามการ์ดเรนเดอร์ บัตรของขวัญอยู่นิ่ง

01 — ง่าย

ความสามารถเท่าเดิม แนวคิดน้อยลง

การเขียนงานเดียวกันสี่แบบวางเทียบกัน ตัวเลขมาจากโค้ดที่แสดงที่นี่และวัดตอนบิลด์เว็บไซต์นี้

ตัวนับที่ใช้ร่วมกัน

สองคอมโพเนนต์อ่านและอัปเดตตัวเลขเดียวกัน

Redux Toolkit จัดการอัปเดตเป็น slice และ action ที่ส่งไปยัง store ที่ให้มาผ่าน context ของ React DevTools ยอดเยี่ยมและลำดับการอัปเดตที่เข้มงวดคาดเดาได้สำหรับทีมขนาดใหญ่

Zustand สร้าง hook ของ store ด้วย create() และเปลี่ยนค่าด้วย setState เล็กมากและยืดหยุ่น มี middleware สำหรับการเก็บข้อมูลถาวรและ DevTools

Jotai ประกอบ atom และ atom ที่สืบทอดค่า จะอยู่ภายใน Provider ก็ได้ การพึ่งพากันอย่างละเอียดระหว่าง state ชิ้นเล็กจำนวนมาก

Redux Toolkit29 บรรทัด · 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 บรรทัด · 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 บรรทัด · 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 การประกาศครั้งเดียวเป็นทั้ง store และ hook

restate13 บรรทัด · 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 />
    </>
  );
}

สำหรับงานนี้ Zustand และ Jotai ก็เล็กและไม่ต้องมี Provider เช่นกัน ความต่างจะเพิ่มขึ้นเมื่อเพิ่มการบันทึกและ state ต่อเอกสาร

การเลือกฟิลด์

คอมโพเนนต์หัวเรื่องไม่ควรเรนเดอร์เมื่อมีแค่ข้อความที่เปลี่ยน

Redux Toolkit จัดการอัปเดตเป็น slice และ action ที่ส่งไปยัง store ที่ให้มาผ่าน context ของ React DevTools ยอดเยี่ยมและลำดับการอัปเดตที่เข้มงวดคาดเดาได้สำหรับทีมขนาดใหญ่

Zustand สร้าง hook ของ store ด้วย create() และเปลี่ยนค่าด้วย setState เล็กมากและยืดหยุ่น มี middleware สำหรับการเก็บข้อมูลถาวรและ DevTools

Jotai ประกอบ atom และ atom ที่สืบทอดค่า จะอยู่ภายใน Provider ก็ได้ การพึ่งพากันอย่างละเอียดระหว่าง state ชิ้นเล็กจำนวนมาก

Redux Toolkit26 บรรทัด · 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 บรรทัด · 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 บรรทัด · 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 ส่ง selector ให้ hook ตัวเดิมที่คุณใช้อยู่แล้ว

restate17 บรรทัด · 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>
    </>
  );
}

เครื่องมือทุกตัวที่นี่เลือกค่าได้ 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 ชิ้นเล็กจำนวนมาก

Redux Toolkit84 บรรทัด · 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 บรรทัด · 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 บรรทัด · 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 กู้คืนอัตโนมัติ และ useStatus รายงานการบันทึก การเปลี่ยนแปลงที่ยังไม่บันทึก และข้อผิดพลาด

restate18 บรรทัด · 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>
    </>
  );
}

เวอร์ชันอื่นเขียนโค้ด 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 ชิ้นเล็กจำนวนมาก

Redux Toolkit42 บรรทัด · 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 บรรทัด · 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 บรรทัด · 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 สร้าง store และทำลายมันหลัง unmount

restate27 บรรทัด · 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 หรือ 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 จริงในเบราว์เซอร์ของคุณ ลองคลิก พิมพ์ และดูตัวนับ

แผนที่การเรนเดอร์

สี่ฟิลด์เดิม อ่านสองแบบ เปลี่ยนฟิลด์หนึ่งแล้วนับการเรนเดอร์

อ่านทั้ง store
  • ชื่อ
  • ธีม
  • ตัวนับ
  • ฉบับร่าง
เลือกฟิลด์เดียว
  • ชื่อ
  • ธีม
  • ตัวนับ
  • ฉบับร่าง

การเขียนแบบรวม

autoSave รอให้หยุดพิมพ์แล้วเขียนครั้งเดียว การเรียก save() ติดกันหลายครั้งใช้การเขียนที่รอคิวร่วมกันครั้งเดียว

persistedRestate('', { key: 'note', autoSave: { delayMs: 400 } });

ตัวเลขมาจากการเรนเดอร์ React จริงและอะแดปเตอร์ที่จัดเก็บข้อมูลที่นับการเขียนบนหน้านี้ ไลบรารีอื่นก็เลือกค่าได้เช่นกัน ที่นี่การเลือกเป็นเพียงวิธีอ่าน

03 — เสถียร

เมื่อที่จัดเก็บข้อมูลล้มเหลว ข้อมูลของคุณยังอยู่ที่เดิม

state หายไปตอนบันทึกพอดี: ดิสก์ช้า ที่จัดเก็บถูกบล็อก แท็บถูกปิด นี่คือสิ่งที่ restate 1.0 รับประกัน ไม่ใช่คำแนะนำ

เปิดห้องทดลองความล้มเหลว อ่านคู่มือความน่าเชื่อถือ
  1. 01

    การบันทึกทำตามลำดับ

    แต่ละ store ทำงานกับที่จัดเก็บข้อมูลทีละอย่าง การบันทึกที่ช้าจึงไม่มีทางทำให้ค่าเก่าเขียนทับค่าที่ใหม่กว่า

    save() · restore()
  2. 02

    การบันทึกที่รออยู่จะใช้ค่าล่าสุด

    การเรียก save() ขณะที่การบันทึกอื่นรอคิวอยู่จะรวมเข้ากับการบันทึกนั้น แทนที่จะสะสมการเขียนเพิ่ม

    save(): Promise<T>
  3. 03

    ระเบียนที่ยังไม่ได้อ่านจะไม่ถูกแทนที่โดยไม่ตั้งใจ

    หากโหลดระเบียนไม่สำเร็จ autoSave และการบันทึกแบบกลุ่มจะไม่แตะต้องมันและรายงาน LOAD_FAILED มีเพียง save() ที่เรียกโดยตรงเท่านั้นที่แทนที่ได้

    LOAD_FAILED
  4. 04

    แม้ unmount แล้ว การบันทึกก็ยังทำจนเสร็จ

    การบันทึกที่ขอไว้ก่อน unmount จะทำจนเสร็จ การเขียนหลังจากนั้นลงใน store ที่ถูกทำลายแล้วจะถูกเพิกเฉย เหมือน state ของ React หลัง unmount

    useRestateScope
  5. 05

    ไม่มีที่จัดเก็บข้อมูล? แอปก็ยังเริ่มทำงาน

    ระหว่างการเรนเดอร์ฝั่งเซิร์ฟเวอร์หรือเมื่อที่จัดเก็บข้อมูลถูกบล็อก store จะทำงานต่อในหน่วยความจำและรายงานว่าที่จัดเก็บข้อมูลใช้ไม่ได้

    status().available
  6. 06

    ข้อผิดพลาดบอกว่าอะไรและที่ไหน

    ข้อผิดพลาดของ restate ทุกตัวมีรหัสแบบมีชนิดและ key ของระเบียน เพื่อให้คุณแสดงข้อความที่ถูกต้องได้

    error.code · error.key

เติบโตไปพร้อมกับแอปของคุณ

เปลี่ยนการประกาศ คอมโพเนนต์คงเดิม

การบันทึก การบันทึกอัตโนมัติ และการตรวจสอบความถูกต้องเป็นตัวเลือกของ store เดียวกัน คอมโพเนนต์ทางขวาไม่เคยเปลี่ยน

  1. หน่วยความจำ

    ค่าที่ใช้ร่วมกันในหน่วยความจำ

    import { restate } from '@kkwaenggwari/restate';
    
    export const useDraft = restate('');
  2. แบบบันทึก

    กู้คืนอัตโนมัติ เขียนเมื่อคุณเรียก save()

    import { persistedRestate } from '@kkwaenggwari/restate';
    
    export const useDraft = persistedRestate('', { key: 'draft' });
  3. บันทึกอัตโนมัติ

    เขียนครั้งเดียวหลังหยุดพิมพ์ 500 ms

    import { persistedRestate } from '@kkwaenggwari/restate';
    
    export const useDraft = persistedRestate('', {
      key: 'draft',
      autoSave: { delayMs: 500 },
    });
  4. พร้อมการตรวจสอบ

    ระเบียนเก่าจะถูกย้ายรูปแบบและตรวจสอบก่อนใช้งาน

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

    React

    ทำงานกับ React 16.8 ขึ้นไป ทั้งในเบราว์เซอร์และระหว่างการเรนเดอร์ฝั่งเซิร์ฟเวอร์

  • react-native-mmkv >=2.0.0

    React Native

    import เดียวกัน การบันทึกจะใช้ MMKV ทันทีที่คุณติดตั้ง react-native-mmkv

  • SSR

    การเรนเดอร์ฝั่งเซิร์ฟเวอร์

    store ของคอมโพเนนต์รับสแนปช็อตจากเซิร์ฟเวอร์ผ่าน props และเริ่มโหลดหลัง hydration

  • index.d.ts

    TypeScript

    มีการประกาศชนิดมาให้ selector ฟิลด์สถานะ และรหัสข้อผิดพลาดล้วนมีชนิด

เริ่มต้นใช้งาน

state ที่ใช้ร่วมกันตัวแรกของคุณภายในหนึ่งนาที

ติดตั้ง ประกาศ แล้วใช้ เพิ่มการบันทึกเมื่อแอปต้องการ

  1. 01
    ติดตั้ง
    Terminal
    npm install @kkwaenggwari/restate
  2. 02
    ประกาศ store
    JavaScript / JSX
    export const useCart = restate({ items: 0, total: 0 });
  3. 03
    อ่านในคอมโพเนนต์ใดก็ได้
    JavaScript / JSX
    const items = useCart((cart) => cart.items);

KKWAENGGWARI

โปรเจกต์

ค้นหาเอกสาร

ค้นหาในชื่อเรื่อง คำอธิบาย และเนื้อหาของคู่มือทั้งหมด