restate / v1.0.0

เทียบกับเครื่องมืออื่น

งานเดียวกันที่เขียนด้วย Redux Toolkit, Zustand, Jotai และ restate วิธีนับตัวเลข และเมื่อใดที่เครื่องมืออื่นเหมาะกว่า

ในหน้านี้เราเปรียบเทียบอย่างไรงานเดียวกัน สี่วิธีเมื่อใดที่เครื่องมืออื่นเหมาะกว่า

เราเปรียบเทียบอย่างไร

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

ไลบรารีอื่นก็เลือกค่า ทำงานโดยไม่ต้องมี Provider หรือบันทึก state ได้เช่นกัน การเปรียบเทียบนี้แสดงว่าผลลัพธ์เดียวกันต้องใช้โค้ดมากแค่ไหน ไม่ได้แสดงว่ามีความสามารถใดบ้าง

งานเดียวกัน สี่วิธี

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

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

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

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 ของคุณโดยธรรมชาติเป็นกราฟของค่าที่สืบทอดขนาดเล็ก

เมื่อใดที่เครื่องมืออื่นเหมาะกว่า

restate มีไว้สำหรับ state ของแอปที่คอมโพเนนต์ใช้ร่วมกันและบางครั้งต้องบันทึก เครื่องมืออื่นอาจเหมาะกว่า:

  • Redux Toolkit เมื่อคุณต้องการให้ทุกการเปลี่ยนแปลงผ่าน action และ reducer พร้อมการดีบักย้อนเวลา
  • Zustand เมื่อคุณต้องการแกนที่เล็กที่สุดและสร้างการเก็บข้อมูลถาวรด้วย middleware ที่เลือกเอง
  • Jotai เมื่อ state ของคุณเป็นกราฟของค่าที่สืบทอดขนาดเล็กจำนวนมาก
  • ไลบรารีข้อมูลฝั่งเซิร์ฟเวอร์ เมื่อข้อมูลอยู่ที่เซิร์ฟเวอร์เป็นหลักและต้องการแคชและการดึงซ้ำ

ค้นหาเอกสาร

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