Локальное и общее состояние — Примеры
Пример 1 — Два компонента, синхронизирующие один факт
Поле поиска и счётчик результатов держат по собственной копии запроса и синхронизируют их эффектом. Стоит быстро нажать дважды, и значения у них разойдутся. Скажите, что скрывает эффект, и дайте значению одного владельца.
function SearchField() {
const [query, setQuery] = useState("");
return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}
function ResultCount({ results }) {
const [query, setQuery] = useState("");
const [count, setCount] = useState(0);
useEffect(() => {
setCount(results.filter((r) => r.name.includes(query)).length);
}, [results, query]);
return <span>{count} shown</span>;
}
Решение
function Page({ results }) {
const [query, setQuery] = useState("");
const count = results.filter((r) => r.name.includes(query)).length;
return (
<>
<SearchField value={query} onChange={setQuery} />
<ResultCount count={count} />
</>
);
}
Эффект, который поддерживает синхронность двух копий одного факта, выполняется после рендера, а не во время него, поэтому взаимодействие, меняющее оба значения в одном такте, оставляет копии в разногласии — вот почему баг выглядит как проблема отрисовки. Подъём означает перенос владельца, а не добавление синхронизатора: значение уходит к ближайшему общему предку, тот передаёт вниз значение и наверх сеттер, а локальные копии удаляются. Счётчик после этого не хранится, а выводится во время рендера, поэтому устареть он не может вовсе.
Пример 2 — Запись хранилища, которую читает только один экран
Выбранную строку одной таблицы хранилище держит вместе с эффектом, который сбрасывает её при размонтировании экрана. Больше это значение не читает никто. Скажите, чего стоит хранилище, и верните значение туда, где его используют.
// store.js
export const useAppStore = create((set) => ({
selectedRow: null,
setSelectedRow: (id) => set({ selectedRow: id }),
resetSelection: () => set({ selectedRow: null }),
}));
// OrdersTable.jsx
useEffect(() => () => resetSelection(), []); // очистка для локального значения
Решение
function OrdersTable() {
const [selectedRow, setSelectedRow] = useState(null);
// сбрасывать нечего: размонтирование забирает значение с собой
return <table>{/* строки вызывают setSelectedRow(row.id) */}</table>;
}
Экран живёт меньше, чем глобальное хранилище, поэтому значение, принадлежащее одному экрану, приходится чистить вручную. Эта очистка и есть признак того, что общим значение никогда не было. С локальным хранением сбрасывать нечего, сериализовать нечего, и нет записи, которую следующий экран может случайно прочитать: время жизни значения совпадает со временем жизни компонента. Направление движения — от локального наружу: поднимайте значение только тогда, когда появился второй, далёкий читатель или когда оно должно пережить навигацию. Хранилище заслуживает своё место благодаря сквозной задаче, а не важности значения.