Tila Reactille ja React Nativelle · 1.0

Jaa tila.Yksi hook.Valmis.

Määrittele store yhdellä rivillä ja käytä sitä missä tahansa komponentissa ilman Provideria, actioneita ja reducereita. Komponentti renderöityy uudelleen vain, kun sen valitsema arvo muuttuu, ja tallennus tapahtuu, kun päätät.

Terminal
npm install @kkwaenggwari/restate

Live — oikea restate-store

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

items ja total muuttuvat: kolme korttia renderöityy, lahjakortti pysyy paikallaan.

01 — Yksinkertainen

Sama toiminto, vähemmän käsitteitä.

Saman tehtävän neljä toteutusta rinnakkain. Luvut tulevat tässä näytetystä koodista, ja ne mitataan tämän sivuston koonnin yhteydessä.

Jaettu laskuri

Kaksi komponenttia lukee ja päivittää samaa lukua.

Redux Toolkit Järjestää päivitykset sliceiksi ja actioneiksi, jotka lähetetään React-kontekstin kautta tarjottuun storeen. Erinomaiset DevTools-työkalut ja tiukka, ennustettava päivityskulku suurille tiimeille.

Zustand Luo store-hookin create()-funktiolla ja muuttaa sitä setStatella. Pienenpieni ja joustava, mukana middlewaret pysyvyyteen ja DevTools-työkaluihin.

Jotai Kokoaa atomeja ja johdettuja atomeja, halutessa Providerin sisällä. Hienojakoiset riippuvuudet monien pienten tilapalojen välillä.

Redux Toolkit29 riviä · 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 riviä · 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 riviä · 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 Yksi määrittely on sekä store että hook.

restate13 riviä · 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 />
    </>
  );
}

Tähän tehtävään myös Zustand ja Jotai ovat pieniä ja toimivat ilman Provideria. Ero kasvaa, kun mukaan tulevat tallennus ja dokumenttikohtainen tila.

Kentän valitseminen

Otsikkokomponentin ei pitäisi renderöityä, kun vain teksti muuttuu.

Redux Toolkit Järjestää päivitykset sliceiksi ja actioneiksi, jotka lähetetään React-kontekstin kautta tarjottuun storeen. Erinomaiset DevTools-työkalut ja tiukka, ennustettava päivityskulku suurille tiimeille.

Zustand Luo store-hookin create()-funktiolla ja muuttaa sitä setStatella. Pienenpieni ja joustava, mukana middlewaret pysyvyyteen ja DevTools-työkaluihin.

Jotai Kokoaa atomeja ja johdettuja atomeja, halutessa Providerin sisällä. Hienojakoiset riippuvuudet monien pienten tilapalojen välillä.

Redux Toolkit26 riviä · 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 riviä · 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 riviä · 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 Anna selector samalle hookille, jota jo käytät.

restate17 riviä · 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>
    </>
  );
}

Kaikki tässä esitellyt työkalut osaavat valita. restate käyttää samaa hook-kutsua erillisen selector-hookin tai johdetun atomin sijaan.

Luonnoksen tallentaminen

Palauta tallennettu luonnos käynnistyksessä, tallenna napsautuksella ja näytä tallennetaan-, tallentamaton- ja epäonnistui-tilat.

Redux Toolkit Järjestää päivitykset sliceiksi ja actioneiksi, jotka lähetetään React-kontekstin kautta tarjottuun storeen. Erinomaiset DevTools-työkalut ja tiukka, ennustettava päivityskulku suurille tiimeille.

Zustand Luo store-hookin create()-funktiolla ja muuttaa sitä setStatella. Pienenpieni ja joustava, mukana middlewaret pysyvyyteen ja DevTools-työkaluihin.

Jotai Kokoaa atomeja ja johdettuja atomeja, halutessa Providerin sisällä. Hienojakoiset riippuvuudet monien pienten tilapalojen välillä.

Redux Toolkit84 riviä · 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 riviä · 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 riviä · 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 palauttaa automaattisesti, ja useStatus raportoi tallennuksen, tallentamattomat muutokset ja virheet.

restate18 riviä · 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>
    </>
  );
}

Muut versiot kirjoittavat saman IndexedDB-koodin käsin. Niiden pysyvyysmiddlewaret tai tallennustyökalut voivat lyhentää sitä sovittimella; sitä koodia ei lasketa tässä.

Dokumenttikohtainen tila

Kaksi dokumenttia pitää itsenäistä tilaa, ja toisen sulkeminen vapauttaa sen.

Redux Toolkit Järjestää päivitykset sliceiksi ja actioneiksi, jotka lähetetään React-kontekstin kautta tarjottuun storeen. Erinomaiset DevTools-työkalut ja tiukka, ennustettava päivityskulku suurille tiimeille.

Zustand Luo store-hookin create()-funktiolla ja muuttaa sitä setStatella. Pienenpieni ja joustava, mukana middlewaret pysyvyyteen ja DevTools-työkaluihin.

Jotai Kokoaa atomeja ja johdettuja atomeja, halutessa Providerin sisällä. Hienojakoiset riippuvuudet monien pienten tilapalojen välillä.

Redux Toolkit42 riviä · 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 riviä · 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 riviä · 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 luo storet ja tuhoaa ne irrottamisen jälkeen.

restate27 riviä · 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" />
    </>
  );
}

Jotain Provider tai store ja Zustandin vanilla-storet voivat myös eristää tilan; luot ja kytket ne itse.

Miten nämä luvut lasketaan

Näytetyn koodin ei-tyhjät rivit ja kustakin tilakirjastosta importatut nimet (React-importteja ei lasketa). Jokainen versio renderöi saman UI:n samalla tallennustilalla sekä samoilla lataus- ja virhetiloilla. restate-esimerkit olettavat await restateManager.ready -kutsun sovelluksen aloituspisteessä. Lähteet on tarkistettu kunkin projektin virallisesta dokumentaatiosta:

  • Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Valitse se, kun tärkeintä on tarkastettava action-loki ja laaja middleware-ekosysteemi
  • Zustand — Zustand · Introduction, Zustand · persist. Valitse se, kun haluat pienimmän ytimen ja kokoat mieluummin middlewaret itse
  • Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Valitse se, kun tilasi on luonteeltaan pienten johdettujen arvojen verkko

02 — Nopea

Renderöi vain muuttunut. Kirjoita kerran.

Molemmat paneelit ajavat oikeaa restate-pakettia selaimessasi. Napsauta, kirjoita ja seuraa laskureita.

Renderöintikartta

Samat neljä kenttää luettuna kahdella tavalla. Muuta kenttää ja laske renderöinnit.

Lukee koko storen
  • Nimi
  • Teema
  • Laskuri
  • Luonnos
Valitsee yhden kentän
  • Nimi
  • Teema
  • Laskuri
  • Luonnos

Yhdistetyt kirjoitukset

autoSave odottaa taukoa kirjoittamisessa ja kirjoittaa sitten kerran. Peräkkäiset save()-kutsut jakavat yhden jonossa olevan kirjoituksen.

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

Luvut tulevat oikeista React-renderöinneistä ja tallennussovittimesta, joka laskee kirjoitukset tällä sivulla. Muutkin kirjastot osaavat valita arvoja; tässä valitseminen on vain tapa lukea.

03 — Vakaa

Kun tallennus pettää, tietosi pysyvät paikallaan.

Tila katoaa juuri tallennettaessa: hitaat levyt, estetty tallennustila, suljetut välilehdet. Nämä ovat restate 1.0:n takuita, eivät neuvoja.

Avaa vikalaboratorio Lue luotettavuusopas
  1. 01

    Tallennukset suoritetaan järjestyksessä

    Jokainen store suorittaa tallennustoiminnot yksi kerrallaan, joten hidas tallennus ei koskaan anna vanhan arvon korvata uudempaa.

    save() · restore()
  2. 02

    Odottava tallennus ottaa uusimman arvon

    Kun kutsut save()-funktiota toisen tallennuksen ollessa jonossa, se liittyy siihen sen sijaan, että kasaisi ylimääräisiä kirjoituksia.

    save(): Promise<T>
  3. 03

    Lukemattomia tietueita ei koskaan korvata vahingossa

    Jos tietue ei latautunut, autoSave ja joukkotallennukset eivät koske siihen vaan raportoivat LOAD_FAILED. Vain nimenomainen save() voi korvata sen.

    LOAD_FAILED
  4. 04

    Tallennus valmistuu irrottamisen jälkeenkin

    Ennen irrottamista pyydetyt tallennukset valmistuvat. Myöhemmät kirjoitukset tuhottuun storeen ohitetaan, kuten Reactin tila irrottamisen jälkeen.

    useRestateScope
  5. 05

    Ei tallennustilaa? Sovellus käynnistyy silti

    Palvelinrenderöinnin aikana tai kun tallennustila on estetty, storet toimivat edelleen muistissa ja ilmoittavat, ettei tallennustila ole käytettävissä.

    status().available
  6. 06

    Virheet kertovat mitä ja missä

    Jokaisessa restate-virheessä on tyypitetty koodi ja tietueen key, jotta voit näyttää oikean viestin.

    error.code · error.key

Kasvaa sovelluksesi mukana

Muuta määrittelyä. Pidä komponentit.

Tallennus, automaattinen tallennus ja validointi ovat saman storen asetuksia. Oikealla oleva komponentti ei muutu koskaan.

  1. Muisti

    Jaettu arvo muistissa.

    import { restate } from '@kkwaenggwari/restate';
    
    export const useDraft = restate('');
  2. Tallennettu

    Palautuu automaattisesti; kirjoitetaan, kun kutsut save()-funktiota.

    import { persistedRestate } from '@kkwaenggwari/restate';
    
    export const useDraft = persistedRestate('', { key: 'draft' });
  3. Automaattitallennus

    Kirjoitetaan kerran 500 ms:n kirjoitustauon jälkeen.

    import { persistedRestate } from '@kkwaenggwari/restate';
    
    export const useDraft = persistedRestate('', {
      key: 'draft',
      autoSave: { delayMs: 500 },
    });
  4. Validoitu

    Vanhemmat tietueet siirretään ja tarkistetaan ennen käyttöä.

    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',
    });

Komponentti — sama jokaisessa vaiheessa

function Editor() {
  const text = useDraft();

  return <textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />;
}

Toimii kaikkialla, missä React toimii

Yksi paketti jokaiseen React-ympäristöön.

  • react >=16.8.0

    React

    Toimii React 16.8:n ja uudempien kanssa selaimessa ja palvelinrenderöinnin aikana.

  • react-native-mmkv >=2.0.0

    React Native

    Samat importit. Tallennus käyttää MMKV:tä heti, kun asennat react-native-mmkv-paketin.

  • SSR

    Palvelinrenderöinti

    Komponenttien storet ottavat palvelimen tilannekuvan propseista ja aloittavat latauksen hydraation jälkeen.

  • index.d.ts

    TypeScript

    Tyyppimäärittelyt sisältyvät. Selectorit, tilakentät ja virhekoodit on tyypitetty.

Aloittaminen

Ensimmäinen jaettu tilasi minuutissa.

Asenna, määrittele, käytä. Lisää tallennus, kun sovellus tarvitsee sitä.

  1. 01
    Asennus
    Terminal
    npm install @kkwaenggwari/restate
  2. 02
    Määrittele store
    JavaScript / JSX
    export const useCart = restate({ items: 0, total: 0 });
  3. 03
    Lue sitä missä tahansa komponentissa
    JavaScript / JSX
    const items = useCart((cart) => cart.items);

KKWAENGGWARI

Projektit

Hae dokumentaatiosta

Hae kaikkien oppaiden otsikoista, kuvauksista ja sisällöstä.