在 localStorage 中持久化 React 状态 显示代码 工作原理 总结

2025-06-07

在 localStorage 中持久化 React 状态

显示代码

工作原理

总结

这篇文章来自我的个人博客。去那里看看,至少能多出35%的奇思妙想!

假设我们正在开发一款日历应用,例如 Google 日历。该应用支持在三种不同的显示方式之间切换:月、周、日。

快速截取 Google 日历的屏幕截图,在日视图和周视图之间切换

就我个人而言,我总是想查看“周”视图。它不仅能让我了解当天的所有信息,还能让我提前了解接下来几天的情况。

值得庆幸的是,日历应用知道用户对这类功能有很强的偏好,所以切换按钮是固定的。如果我从“周”切换到“月”,并刷新页面,“月”视图就会成为新的默认视图;它会一直保留。

相反,如果表单控件不够灵活,那就太烦人了。比如,我每个月都会通过 Expensify 创建 4-5 笔支出。每次我都得把默认货币从美元换成加元。为什么它记不住我是加拿大人?

在本教程中,我们将了解如何创建自定义 React hook来抽象出“粘性”,以便我们在需要时可以免费获得它。

显示代码

我们的自定义钩子如下所示:

function useStickyState(defaultValue, key) {
  const [value, setValue] = React.useState(() => {
    const stickyValue =
      window.localStorage.getItem(key);

    return stickyValue !== null
      ? JSON.parse(stickyValue)
      : defaultValue;
  });

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

  return [value, setValue];
}

那么 SSR 呢?如果你的应用是服务端渲染的(使用 Next.js 或 Gatsby 之类的框架),那么直接使用此钩子会报错。

这实际上是一个非常棘手的问题,因为服务器上的第一次渲染无法访问您计算机的本地存储;它不可能知道初始值应该是什么!

服务器渲染应用中的动态内容是一个复杂的主题,但幸运的是,我的下一篇博文将会对此进行一些阐述!订阅我的新闻通讯,确保您不会错过。

如果你不明白这段代码,别担心!本教程的其余部分将更详细地解释它 💫

实践

这个钩子做了一个假设,这在 React 应用程序中是相当安全的:表单输入的值保持在 React 状态中。

以下是用于在值之间切换的表单控件的非粘性实现:

const CalendarView = () => {
  const [mode, setMode] = React.useState('day');

  return (
    <>
      <select onChange={ev => setMode(ev.target.value)}>
        <option value="day">Day</option>
        <option value="week">Week</option>
        <option value="month">Month</option>
      </select>

      {/* Calendar stuff here */}
    </>
  )
}

我们可以通过交换钩子来使用新的“粘性”变体:

const CalendarView = () => {
  const [mode, setMode] = useStickyState('day', 'calendar-view');

  // Everything else unchanged
}

虽然这个useState钩子只接受一个参数——初始值,但我们的useStickyState钩子接受两个参数。第二个参数是用于获取和设置 localStorage 中持久化值的键。你赋予它的标签必须是唯一的,但除此之外,它是什么都无所谓。

工作原理

从根本上来说,这个钩子是对 的包装useState。它还做了一些其他的事情。

延迟初始化

首先,它利用了延迟初始化。这允许我们传递一个函数而useState不是一个值,并且该函数只会在组件第一次渲染时(即状态创建时)执行。

const [value, setValue] = React.useState(() => {
  const stickyValue =
    window.localStorage.getItem(key);

  return stickyValue !== null
    ? JSON.parse(stickyValue)
    : defaultValue;
});

在我们的例子中,我们用它来检查 localStorage 中的值。如果值存在,我们就用它作为初始值。否则,我们就使用传递给 hook 的默认值(在之前的例子中是“day”)。

保持 localStorage 同步

最后一步是确保每当状态值发生变化时更新 localStorage。为此,我们值得信赖的朋友useEffect派上用场了:

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

如果状态值变化很快(比如一秒钟多次),您可能希望限制或消除对 localStorage 的更新。由于 localStorage 是一个同步 API,如果更新速度过快,可能会导致性能问题。

不过,不要以此为借口过早进行优化!分析器会显示是否需要限制更新。

总结

这个钩子虽然小巧,但却功能强大,它展现了自定义钩子如何帮助我们创建自己的 API。虽然一些包 可以帮我们解决这个问题,但我认为学习如何自己解决这些问题仍然很有价值🧙🏻‍♂️

特别感谢 Satyajit Sahoo 提出的一些重构建议🌠

文章来源:https://dev.to/joshwcomeau/persisting-react-state-in-localstorage-32p5
PREV
关于 Git 和 Vim 的一个简单但强大的技巧
NEXT
使用单一域名管理你的业余项目!感谢阅读