如何使用 localStorage Hook 在 React 中持久化状态

React localStorage Hook 是一个自定义 Hook,它将 React 组件状态与浏览器的 localStorage API 同步,使数据能够在页面刷新和浏览器会话之间持久保存。它提供了类似 useState 的接口,自动处理序列化、错误恢复和 SSR 安全性,无需手动读取、写入和解析存储的值。

问题所在

React 状态是短暂的。当用户刷新页面或关闭浏览器标签页时,存储在 useState 中的任何状态都会丢失。对于用户偏好、表单草稿、购物车项目或认证令牌等数据,这是一种糟糕的体验。

浏览器的 localStorage API 提供了一个简单的持久化层,但将它与 React 集成会带来几个挑战:

  1. 值必须被序列化和反序列化(localStorage 只存储字符串)
  2. 在服务端渲染期间读取 localStorage 会导致错误
  3. 保持 React 状态和 localStorage 同步需要小心管理副作用
  4. 多个标签页可能修改同一个键,导致状态过期

手动方式

以下是开发者通常手动接入 localStorage 持久化的方式:

import { useEffect, useState } from "react";

function useManualLocalStorage(key: string, defaultValue: string) {
  const [value, setValue] = useState(() => {
    if (typeof window === "undefined") return defaultValue;
    const stored = localStorage.getItem(key);
    return stored !== null ? JSON.parse(stored) : defaultValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

这涵盖了基础功能,但仍有不足。它不处理序列化错误,不监听通过 storage 事件的跨标签页变化,不支持复杂数据类型的自定义序列化器,而且每次需要持久化时都要重复这些逻辑。

更好的方式:useLocalStorage

ReactUse 提供了 useLocalStorage Hook,一次导入就能处理上述所有问题:

import { useLocalStorage } from "@reactuses/core";

function ThemeSettings() {
  const [theme, setTheme] = useLocalStorage("theme", "light");

  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={() => setTheme("dark")}>Dark Mode</button>
      <button onClick={() => setTheme("light")}>Light Mode</button>
    </div>
  );
}

该 Hook 返回一个与 useState 相同的元组——当前值和一个设置函数。底层它在挂载时从 localStorage 读取,在每次更新时写入,并在 SSR 或 localStorage 不可用时优雅地回退到默认值。

它支持字符串、数字、布尔值和对象。类型推断是自动的:

import { useLocalStorage } from "@reactuses/core";

// Type is inferred as number | null
const [count, setCount] = useLocalStorage("visit-count", 0);

// Type is inferred as boolean | null
const [accepted, setAccepted] = useLocalStorage("cookie-consent", false);

// Type is inferred as { name: string; role: string } | null
const [user, setUser] = useLocalStorage("user", { name: "", role: "viewer" });

高级用法

自定义序列化器

默认情况下,useLocalStorage 使用 JSON.parseJSON.stringify。如果你需要以不同格式存储数据——例如日期或自定义类——你可以提供自定义序列化器:

import { useLocalStorage } from "@reactuses/core";

const [lastVisit, setLastVisit] = useLocalStorage("last-visit", new Date(), {
  serializer: {
    read: (raw: string) => new Date(raw),
    write: (value: Date) => value.toISOString(),
  },
});

跨标签页同步

该 Hook 默认监听浏览器的 storage 事件,因此如果用户在一个标签页中更新了值,所有其他打开的标签页会立即反映变化。如果需要,你可以禁用此功能:

const [token, setToken] = useLocalStorage("auth-token", "", {
  listenToStorageChanges: false,
});

SSR 安全性

因为 useLocalStorage 在访问 localStorage 之前会检查浏览器可用性,所以它可以直接在 Next.js、Remix 和任何其他 SSR 框架中使用。在服务端渲染期间,Hook 返回默认值而不会抛出错误。

错误处理

如果 localStorage 已满、被浏览器策略阻止或包含损坏的数据,Hook 会优雅地捕获错误。你可以提供自定义的错误处理器:

const [data, setData] = useLocalStorage("app-data", null, {
  onError: (error) => {
    console.warn("Storage error:", error);
    // Send to your error tracking service
  },
});

常见使用场景

  • 主题和外观偏好 — 跨会话持久化深色/浅色模式
  • 表单草稿 — 保存进行中的表单数据,以免用户在刷新时丢失工作
  • 认证令牌 — 在页面加载之间存储 JWT 或会话令牌
  • 功能标志和引导状态 — 记住用户已关闭的提示
  • 购物车内容 — 无需后端即可保持购物车项目完好
  • 语言和区域设置 — 记住用户偏好的语言

安装

npm i @reactuses/core

然后导入 Hook:

import { useLocalStorage } from "@reactuses/core";

相关 Hooks


ReactUse 提供了 100 多个 React Hooks。查看全部 →