tgoop.com/prog_way_blog/345
Create:
Last Update:
Last Update:
Связываем React и localStorage через useSyncExternalStore
Как согласовать изменение состояния в реакте и поля в localStorage
? До недавнего времени самым простым вариантом было создать контекст с внутренним React состоянием и обрабатывать всё взаимодействие с localStorage
через него — вариант рабочий, но далеко не идеален: легко напороться на ререндеры, много кода писать нужно ну и вот это вот всё
Также можно обработать какое-то не-реактовое значение через комбинацию useState
+ useEffect
, но это ещё менее надёжно, ведь браузерные значения могут меняться и без уведомления реакта, и, соответственно, без ререндера
Красиво в одной из статей на хабре описали:
Для работы с состоянием в React используются хуки useState и useReducer, но они не умеют работать с состоянием, которое "живет" за пределами React, поскольку в один момент времени доступна только одна версия внешнего состояния.
Значения внешнего состояния могут меняться со временем без ведома React, что может приводить к таким проблемам, как отображение двух разных значений для одних и тех же данных.
Статья: https://habr.com/ru/companies/timeweb/articles/720136/
Но не так давно в 18 версию
React
добавили хук useSyncExternalStore
, который такую задачу решает намного изящнееМногие скипнули его и даже не знают зачем он нужен, что, в целом, достаточно ожидаемо, ведь даже команда разработчиков позиционировала его больше как хук для разработчиков библиотек, а мы тут далеко не все пишем свои либы
Короче, что это за хук вообще? Очень просто — этот хук нужен для более глубокой интеграции внешних хранилищ в модель React. Говоря проще — хук нужен для того, чтобы триггерить рендер из внешних хранилищ, а не только через
setState
функции Как раз этот хук и поможет нам интегрироваться с
localStorage
сильно проще и безопаснее. Тут localStorage
в понятие внешнего хранилища ложится просто шикарноНа коленке код будет выглядеть примерно так:
const useLocalStorageState = (key: string, defaultValue?: string) => {
const subscribe = (listener: () => void) => {
window.addEventListener("update-local-storage", listener);
return () => void window.removeEventListener("update-local-storage", listener);
};
const getSnapshot = () => localStorage.getItem(key) ?? defaultValue;
const store = useSyncExternalStore(subscribe, getSnapshot);
const updateStore = (newValue: string) => {
localStorage.setItem(key, newValue);
window.dispatchEvent(new StorageEvent("update-local-storage", { key, newValue }));
};
return [store, updateStore] as const;
};
В чём тут идея:
1. При вызове
updateStore
будем помимо изменения значения в localStorage
диспатчить на window
ещё и StorageEvent
с ключом, например, "update-local-storage"
2. В функции подписки
subscribe
объясним когда нужно вызывать getSnapshot
для получения актуального состояния из внешнего хранилища и когда от его прослушивания нужно отписаться. Можно воспринимать как эффектИспользовать будем как обычный
useState
:const [name, setName] = useLocalStorageState("name", "progway");
Теперь хук при вызове с одним и тем же ключом к
localStorage
(name
в примере выше) будет обновлять все зависимые компоненты при регистрации события "update-local-storage"
на window
Используя тот же подход, можно реализовать порой очень полезные хуки
useMediaQuery
, useWindowSize
и другие. О первых двух можно прочитать в статье от Timeweb CloudСпасибо за прочтение, это важно для меня
@prog_way_blog — чат — #web #javascript #theory #data #code #react