Състояние за React и React Native · 1.0

Споделете състояние.Един hook.Готово.

Декларирайте 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, предоставен чрез контекста на React. Отлични DevTools и строг, предвидим поток на обновяване за големи екипи.

Zustand Създава hook за store с create() и го променя със setState. Мъничък и гъвкав, с middleware за запазване и DevTools.

Jotai Сглобява атоми и производни атоми, по желание в Provider. Фини зависимости между много малки парчета състояние.

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. Разликата расте, когато се добавят запазване и състояние за всеки документ.

Избор на поле

Компонентът на заглавието не бива да се пререндерира, когато се промени само текстът.

Redux Toolkit Организира обновяванията в slice и action, изпратени до store, предоставен чрез контекста на React. Отлични DevTools и строг, предвидим поток на обновяване за големи екипи.

Zustand Създава hook за store с create() и го променя със setState. Мъничък и гъвкав, с middleware за запазване и DevTools.

Jotai Сглобява атоми и производни атоми, по желание в Provider. Фини зависимости между много малки парчета състояние.

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 или производен атом.

Запазване на чернова

Възстановете запазена чернова при стартиране, запазете при клик и покажете състоянията запазва се, незапазено и неуспех.

Redux Toolkit Организира обновяванията в slice и action, изпратени до store, предоставен чрез контекста на React. Отлични DevTools и строг, предвидим поток на обновяване за големи екипи.

Zustand Създава hook за store с create() и го променя със setState. Мъничък и гъвкав, с middleware за запазване и DevTools.

Jotai Сглобява атоми и производни атоми, по желание в Provider. Фини зависимости между много малки парчета състояние.

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 за запазване или инструменти за хранилище могат да го съкратят с адаптер; този код тук не се брои.

Състояние за всеки документ

Два документа пазят независимо състояние, а затварянето на единия го освобождава.

Redux Toolkit Организира обновяванията в slice и action, изпратени до store, предоставен чрез контекста на React. Отлични DevTools и строг, предвидим поток на обновяване за големи екипи.

Zustand Създава hook за store с create() и го променя със setState. Мъничък и гъвкав, с middleware за запазване и DevTools.

Jotai Сглобява атоми и производни атоми, по желание в Provider. Фини зависимости между много малки парчета състояние.

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 и ги унищожава след демонтиране.

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 също могат да изолират състояние; създавате и свързвате ги сами.

Как се броят тези числа

Непразните редове на показания код и имената, импортирани от всяка библиотека за състояние (импортите на 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. Изберете го, когато състоянието ви по природа е граф от малки производни стойности

02 — Бързо

Рендерирайте само промененото. Записвайте веднъж.

И двата панела изпълняват истинския пакет restate във вашия браузър. Кликайте, пишете и следете броячите.

Карта на рендерите

Същите четири полета, прочетени по два начина. Променете поле и пребройте рендерите.

Чете целия store
  • Име
  • Тема
  • Брояч
  • Чернова
Избира едно поле
  • Име
  • Тема
  • Брояч
  • Чернова

Обединени записи

autoSave изчаква пауза в писането и после записва веднъж. Поредица от извиквания на save() споделя един запис в опашката.

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

Числата идват от истински рендери на React и адаптер за хранилище, който брои записите на тази страница. Други библиотеки също могат да избират стойности; тук изборът е просто начинът на четене.

03 — Стабилно

Когато хранилището откаже, данните ви остават на мястото си.

Състоянието се губи точно при запазване: бавни дискове, блокирано хранилище, затворени раздели. Това са гаранции на restate 1.0, а не съвети.

Отвори лабораторията за повреди Прочетете ръководството за надеждност
  1. 01

    Запазванията стават по ред

    Всеки store изпълнява операциите с хранилището една след друга, така че бавно запазване никога не позволява стара стойност да презапише по-нова.

    save() · restore()
  2. 02

    Чакащото запазване взема най-новата стойност

    Извикване на save(), докато друго запазване чака в опашката, се присъединява към него, вместо да трупа нови записи.

    save(): Promise<T>
  3. 03

    Непрочетени записи никога не се презаписват по случайност

    Ако запис не се е заредил, autoSave и груповите запазвания не го докосват и съобщават LOAD_FAILED. Само изрично save() може да го замени.

    LOAD_FAILED
  4. 04

    Дори след демонтиране запазването завършва

    Запазванията, поискани преди демонтиране, завършват. По-късни записи в унищожен store се игнорират, точно както състоянието на React след демонтиране.

    useRestateScope
  5. 05

    Няма хранилище? Приложението стартира въпреки това

    При рендериране на сървъра или при блокирано хранилище store продължават да работят в паметта и съобщават, че хранилището не е налично.

    status().available
  6. 06

    Грешките казват какво и къде

    Всяка грешка на restate носи типизиран код и ключа на своя запис, за да покажете правилното съобщение.

    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

    Същите импорти. Запазването използва MMKV веднага щом инсталирате react-native-mmkv.

  • SSR

    Рендериране на сървъра

    Store на компонента вземат моментната снимка от сървъра от props и започват зареждане след хидратация.

  • index.d.ts

    TypeScript

    Декларациите са включени. Селекторите, полетата на статуса и кодовете на грешки са типизирани.

Начало

Първото ви споделено състояние за минута.

Инсталирайте, декларирайте, използвайте. Добавете запазване, когато приложението ви има нужда.

  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

Проекти

Търсене в документацията

Търсене в заглавията, описанията и текста на всички ръководства.