原文链接 Persisting React State in localStorage -- 作者 Joshua Comeau
我们将创建一个日历应用,就像谷歌日历。这个应用可以让我们在月份、周和日之间进行切换。
于我个人而言,我经常看周版面。它让我知道当天的所有事情,并且可以看到接下来几天的要发生什么事情。
值得庆幸的是,日历应用知道用户对这类事情有强烈的偏好,并且切换是“可记忆的(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。
为了演示它是怎么工作的,这里有个固定记数的记数器应用。我们可以尝试点击按钮多次,然后刷新页面。
如果这些代码你看不懂,没关系。本教程接下来会详细解析。💫
这个钩子函数做了一个单一的假设,这在 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
)。
最后一步,确保当我们更改 state
中的值,需要更新 localStorage
。为此,我们可信赖的伙伴 useEffect
派上用场:
React.useEffect(() => {
window.localStorage.setItem(name, JSON.stringify(value));
}, [name, value]);
⚠️ 频繁更新? 如果
state
状态值更改太快(比如,一秒中执行很多次),你可能需要使用节流throttle
或者防抖debounce
来更新localStorage
。因为localStorage
是一个同步 API,如果它更新太频繁,会造成性能问题。 不过,不要以此为借口过早优化。分析器Profiler
会向你展示是否需要限制更新。
这个钩子函数是一个小而强大的例子,说明自定义钩子如何让我们为解决问题而发明自己的 API
。虽然存在帮我们解决这个问题的依赖包,但是我认为了解如何解决这些问题很有价值。