Локальное хранилище — это хорошее место, обычно используемое для хранения данных (но не токенов аутентификации!), которые необходимо сохранять между сеансами.
Вы можете удобно хранить пользовательские настройки, такие как свернутая или развернутая боковая панель, в локальном хранилище. Однако обновления не будут синхронизироваться на нескольких вкладках. Чтобы решить эту проблему, используйте хук useSyncExternalStore
в React, чтобы обеспечить согласованность данных на всех вкладках.
useSyncExternalStore
— это React Hook, который позволяет вам подписаться на внешний магазин.
В нашем контексте внешнее хранилище относится к локальному хранилищу. useSyncExternalStore
позволяет нам устранить разрыв между React и локальным хранилищем путем подписки компонента на локальное хранилище.
Пример с useState + useEffect
Давайте сначала рассмотрим пример плохой практики, которая не работает должным образом ❌:
Код, который используется в примере выше:
import React from "react"; type SidebarState = "collapsed" | "expanded"; const get = () => localStorage.getItem("sidebar") as SidebarState; const set = (value: SidebarState) => localStorage.setItem("sidebar", value); if (!get()) { set("collapsed"); } function App() { const [sidebarState, setSidebarState] = React.useState<SidebarState>(get()); React.useEffect(() => { set(sidebarState); }, [sidebarState]); const handleToggle = () => setSidebarState(sidebarState === "collapsed" ? "expanded" : "collapsed"); return ( <> <p> The sidebar is{" "} <span style={{ color: sidebarState === "collapsed" ? "red" : "green" }}> {sidebarState} </span> </p> <button onClick={handleToggle}>Toggle State</button> </> ); }
- Мы используем локальное хранилище только в качестве начального состояния для React
useState
. Это необходимо для достижения реактивности, иначе React не будет обновлять пользовательский интерфейс (повторно отображать) при прямых обновлениях в локальном хранилище. useState
означает, что состояние связано с этим экземпляром компонента, а это значит, что оно не будет синхронизироваться между вкладками.- Наконец,
useState
означает, что нам необходимо синхронизировать локальное хранилище с изменениями, чтобы при следующей загрузке приложения мы получили последнее установленное состояние.
Пример с использованиемSyncExternalStore
Правильный способ сделать это 💪
Код, который используется в примере выше:
import React from "react"; type SidebarState = "collapsed" | "expanded"; function setSidebarState(newValue: SidebarState) { window.localStorage.setItem("sidebar", newValue); // On localStoage.setItem, the storage event is only triggered on other tabs and windows. // So we manually dispatch a storage event to trigger the subscribe function on the current window as well. window.dispatchEvent( new StorageEvent("storage", { key: "sidebar", newValue }) ); } const store = { getSnapshot: () => localStorage.getItem("sidebar") as SidebarState, subscribe: (listener: () => void) => { window.addEventListener("storage", listener); return () => void window.removeEventListener("storage", listener); }, }; // Set the initial value. if (!store.getSnapshot()) { localStorage.setItem("sidebar", "collapsed" satisfies SidebarState); } function App() { const sidebarState = React.useSyncExternalStore( store.subscribe, store.getSnapshot ); const handleToggle = () => { setSidebarState(sidebarState === "expanded" ? "collapsed" : "expanded"); }; return ( <> <p> The sidebar is <span style={{ color: sidebarState === "collapsed" ? "red" : "green" }}> {sidebarState} </span> </p> <button onClick={handleToggle}>Toggle State</button> </> ); }
useSyncExternalStore
принимает два обязательныхаргумента:
- Функция
subscribe
должна подписаться на хранилище и вернуть функцию, которая отписывается. Аргументlistener
в этой функции автоматически прослушивает событияstorage
и повторно отображает компонент при изменениях. - Функция
getSnapshot
должна прочитать снимок данных из хранилища. Чтобы упростить задачу, вам следует избегать возврата неизменяемых данных (например, объектов), поскольку они различаются при каждом вызовеgetSnapshot
и будут вызывать бесконечные повторные рендеринги. Если вам необходимо, вам следует кэшировать возвращаемое значениеgetSnapshot
.
Эти две функции подключают данные, хранящиеся в локальном хранилище, к React и обеспечивают реактивность между вкладками и окнами.
Бонус: извлеките хранилище в специальный хук.
Наконец, вы можете извлечь логику в собственный хук:
import React from "react"; type SidebarState = "collapsed" | "expanded"; function useSidebarState() { const setSidebarState = (newValue: SidebarState) => { window.localStorage.setItem("sidebar", newValue); window.dispatchEvent( new StorageEvent("storage", { key: "sidebar", newValue }) ); }; const getSnapshot = () => localStorage.getItem("sidebar") as SidebarState; const subscribe = (listener: () => void) => { window.addEventListener("storage", listener); return () => void window.removeEventListener("storage", listener); }; const store = React.useSyncExternalStore(subscribe, getSnapshot); return [store, setSidebarState] as const; }
Краткое содержание
Сегодня мы узнали, что комбинация useState
и useEffect
не является идеальным способом управления состоянием с использованием данных, находящихся в локальном хранилище. Мы можем использовать локальное хранилище в качестве внешнего хранилища, которое взаимодействует с React с помощью useSyncExternalStore
.
Давайте соединимся
Вы можете связаться со мной по следующим ссылкам:
Если вы хотите видеть больше контента о комплексном проектировании, не забудьте поставить лайк и подписаться, чтобы выразить свою поддержку! Спасибо!