在 localStorage 中持久化 React 状态

原文链接 Persisting React State in localStorage — 作者 Joshua Comeau

我们将创建一个日历应用,就像谷歌日历。这个应用可以让我们在月份、周和日之间进行切换。

在 localStorage 中持久化 React 状态

于我个人而言,我经常看版面。它让我知道当天的所有事情,并且可以看到接下来几天的要发生什么事情。

值得庆幸的是,日历应用知道用户对这类事情有强烈的偏好,并且切换是“可记忆的(sticky)”。如果我从切换到,并刷新页面,视图是新的默认视图。

在本教程中,我们将了解如何创建自定义 React 钩子,来编写信息保存本地功能,以便我们在需要时使用它。

展示代码

我们自定义的钩子函数如下:

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));
  });
  
  return [value, setValue];
}

⚠️
SSR 呢?
如果你的应用是服务端渲染(使用框架比如 Next.js 或者 Gatsby),如果你尝试使用该钩子函数,你将会得到一个错误。
这实际上是一个很棘手的问题,因为 SSR 第一次渲染无法访问你浏览器上的 localStorage;它不可能知道初始值应该是什么。
在服务端渲染的应用中,动态内容是一个复杂的课题。但是,我为该课题写了一篇文章。若想了解更多,请前往 The Perils of Rehydration

为了演示它是怎么工作的,这里有个固定记数的记数器应用。我们可以尝试点击按钮多次,然后刷新页面。

在 localStorage 中持久化 React 状态

如果这些代码你看不懂,没关系。本教程接下来会详细解析。💫

实战

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

这里有个表单非固定值的实现,控制不同值之间切换:

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 键(key)值。你给定 key 的值需要唯一。

它怎么工作

基本上,useStickyState 这个钩子函数是 useState 的包装器。只是,它做了一些其他事。

延迟初始化

首先,它发挥了延迟初始化的优势。这使得我们可以给 useState 传递一个函数,而不是一个值。当状态 state 被创建时,这个函数只是在组件第一次渲染被执行。

const [value, setValue] = React.useState(() => {
  const stickyValue =
    window.localStorage.getItem(key);
  return stickyValue !== null
    ? JSON.parse(stickyValue)
    : defaultValue;
});

在我们的案例中,我们使用它来检查 localStorage 中的值。如果值存在,我们将使用该值作为我们的初始值。否则,我们将使用钩子函数传递的默认值(在我们先前的例子中,其默认值是 day)。

保持 localStorage 同步

最后一步,确保当我们更改 state 中的值,需要更新 localStorage 。为此,我们可信赖的伙伴 useEffect 派上用场:

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

⚠️ 频繁更新?
如果 state 状态值更改太快(比如,一秒中执行很多次),你可能需要使用节流 throttle 或者防抖 debounce 来更新 localStorage。因为 localStorage 是一个同步 API,如果它更新太频繁,会造成性能问题。
不过,不要以此为借口过早优化。分析器 Profiler 会向你展示是否需要限制更新。

总结

这个钩子函数是一个小而强大的例子,说明自定义钩子如何让我们为解决问题而发明自己的 API。虽然存在帮我们解决这个问题的依赖包,但是我认为了解如何解决这些问题很有价值。

本文正在参加「金石计划」

原文链接:https://juejin.cn/post/7220944006296141881 作者:Jimmy

(0)
上一篇 2023年4月12日 上午10:16
下一篇 2023年4月12日 上午10:27

相关推荐

发表回复

登录后才能评论