React中的本地存储(详细教程)

2,612 阅读7分钟

在这个React教程中,你将学习如何通过使用一个自定义的React Hook 在本地存储中存储状态。我们很快也会讨论会话存储,但基本上它的使用方式与React中的本地存储是一样的。在阅读关于在React中使用本地存储的内容之前,我将给你一个简单的概述,即如何使用它,以及什么时候只在JavaScript中使用它。

JavaScript中的本地存储

本地存储被现代浏览器所支持。你可以检查浏览器的兼容性,并在官方文档中阅读更多关于它的内容。

如何在JavaScript中使用本地存储?在你的客户端JavaScript中,在浏览器中运行,因此可以访问浏览器的API,你应该可以访问localStorage 实例,该实例有setter和getter方法,可以向本地存储写入和读出数据。

const textForStorage = 'Hello World.'
// setterlocalStorage.setItem('my-key', textForStorage);
// getterconst textFromStorage = localStorage.getItem('my-key');

这两个方法都需要你传递一个字符串(这里是:'my-key' ),它标识了本地存储中的存储值。有了这个键,你可以向本地存储设置或从本地存储获取一个项目。换句话说,第一个参数是写入/读取数据的密钥,而第二个参数--在存储数据时--是实际的数据。

在本地存储中也有一些方法可以删除个别项目和清除所有项目。

// removelocalStorage.removeItem('my-key');
// remove alllocalStorage.clear();

本地存储中的数据在浏览器会话中持续存在,这意味着即使关闭和打开浏览器,这些数据也会保持活力。

需要注意的是,存储在本地存储中的数据应该是JavaScript字符串的格式。例如,如果你想在本地存储中写入和读出一个对象,你需要使用JSON API将其从JavaScript对象转换(JSON.stringify())为JavaScript字符串(写入),并将其从JavaScript字符串转换(JSON.parse())为JavaScript对象(读取)。

const person = { firstName: 'Robin', lastName: 'Wieruch' };
localStorage.setItem('user', JSON.stringify(person));
const stringifiedPerson = localStorage.getItem('user');const personAsObjectAgain = JSON.parse(stringifiedPerson);

在客户端有一个持久的存储,使开发人员能够为他们的应用程序的用户释放许多用户体验。例如,我们可以存储用户的偏好,如灯光/黑暗模式和语言设置,这样用户就可以在浏览器中保持这些设置的半持久性,而不需要处理后台API及其数据库。

React的本地存储

接下来,我们将把注意力集中在使用React的本地存储上。在这个例子中,我们有一个React函数组件,它使用React的useState Hook来管理一个JavaScript布尔基元的状态。这个布尔值是通过一个按钮HTML元素和一个React事件处理程序来切换的。在这个布尔值的帮助下,我们有条件地渲染文本。

import * as React from 'react';
const App = () => {  const [isOpen, setOpen] = React.useState(false);
  const handleToggle = () => {    setOpen(!isOpen);  };
  return (    <div>      <button onClick={handleToggle}>Toggle</button>      {isOpen && <div>Content</div>}    </div>  );};
export default App;

切换

你可以通过点击按钮来切换内容的开与关。然而,如果你刷新浏览器(或关闭并再次打开),你将以false 作为初始状态开始,因为React的useState Hook是这样实现的。那么,在浏览器会话之间为它使用本地存储作为缓存呢?一个解决方案可以像下面这样。

import * as React from 'react';
const App = () => {  const [isOpen, setOpen] = React.useState(    JSON.parse(localStorage.getItem('is-open')) || false  );
  const handleToggle = () => {    localStorage.setItem('is-open', JSON.stringify(!isOpen));
    setOpen(!isOpen);  };
  return (    <div>      <button onClick={handleToggle}>Toggle</button>      {isOpen && <div>Content</div>}    </div>  );};
export default App;

切换

在两个地方,我们建立了本地存储的读和写方法。当我们在React的事件处理程序中把新的布尔状态作为字符串化的值存储到本地存储时,我们从本地存储中读取从字符串到布尔的解析值,作为React的useState Hook中使用的初始状态。如果本地存储中没有值,我们默认初始状态为false

如果本地存储在你的浏览器中是可用的,建议的解决方案是可行的。尝试将打开状态切换到truefalse ,然后刷新浏览器。状态应该保持不变,因为它是随着每次用户交互而存储的,并且在第一次渲染组件并因此初始化其钩子时被检索为初始状态。

然而,建议的解决方案并不是处理React中这种情况(称为副作用)的最佳做法。例如,如果setOpen 状态更新器函数在其他地方被调用怎么办?我们会破坏这个功能,因为我们可能会错过实现向本地存储的写入。我们可以通过使用React的useEffect Hook,在本地存储中反应性地设置isOpen 状态的变化来改进实现。

import * as React from 'react';
const App = () => {  const [isOpen, setOpen] = React.useState(    JSON.parse(localStorage.getItem('is-open')) || false  );
  const handleToggle = () => {    setOpen(!isOpen);  };
  React.useEffect(() => {    localStorage.setItem('is-open', JSON.stringify(isOpen));  }, [isOpen]);
  return (    <div>      <button onClick={handleToggle}>Toggle</button>      {isOpen && <div>Content</div>}    </div>  );};
export default App;

现在,每当isOpen 被改变时,副作用的钩子就会运行并做它的事情(这里:将其保存到本地存储)。

React本地存储钩子

最后但并非最不重要的是,你可以将该功能提取为可重用的自定义React钩子,使本地存储与React的状态同步。

import * as React from 'react';
const useLocalStorage = (storageKey, fallbackState) => {  const [value, setValue] = React.useState(    JSON.parse(localStorage.getItem(storageKey)) ?? fallbackState  );
  React.useEffect(() => {    localStorage.setItem(storageKey, JSON.stringify(value));  }, [value, storageKey]);
  return [value, setValue];};
const App = () => {  const [isOpen, setOpen] = useLocalStorage('is-open', false);
  const handleToggle = () => {    setOpen(!isOpen);  };
  return (    <div>      <button onClick={handleToggle}>Toggle</button>      {isOpen && <div>Content</div>}    </div>  );};
export default App;

通过将该功能提取为可重复使用的钩子,你可以在一个以上的React组件中使用它。每个组件只需要使用一个独特的storageKey ,以避免与其他组件的存储空间相冲突。

无论如何,尽管这个自定义钩子向你展示了它是如何工作的,你应该在你的React生产应用中依靠它的开源变体。在这篇文章中阅读我在我的项目中更喜欢使用哪个useLocalStorage钩子。

React中的会话存储

有时你想只在当前浏览器会话中缓存/保存数据。当关闭浏览器时,你希望缓存再次变空,但当你刷新浏览器标签时,你希望保持缓存的完整性。

例如,在React中处理认证时,用户会话可以保存在会话存储中,直到浏览器被关闭。因此,你会使用浏览器的会话存储而不是本地存储。

const textForStorage = 'Hello World.'
// settersessionStorage.setItem('my-key', textForStorage);
// getterconst textFromStorage = sessionStorage.getItem('my-key');

正如你所看到的,会话存储的使用方式与本地存储相同,只是行为方式不同,不跨浏览器会话持久化存储。

如何在React中缓存数据

让我们在React中进一步使用本地存储,把它作为远程数据的缓存,在浏览器会话中持续存在。因此,在下一个例子中,你将从一个远程API获取数据,并将其存储在React组件的状态中。

继续阅读。如何在React中获取数据

我们将从一个组件开始,从一个流行的API中获取数据。

import * as React from 'react';import axios from 'axios';
const API_ENDPOINT = 'https://hn.algolia.com/api/v1/search?query=';const INITIAL_QUERY = 'react';
const App = () => {  const [data, setData] = React.useState({ hits: [] });  const [query, setQuery] = React.useState(INITIAL_QUERY);  const [url, setUrl] = React.useState(    `${API_ENDPOINT}${INITIAL_QUERY}`  );
  React.useEffect(() => {    const fetchData = async () => {      const result = await axios(url);
      setData({ hits: result.data.hits });    };
    fetchData();  }, [url]);
  return (    <>      <input        type="text"        value={query}        onChange={(event) => setQuery(event.target.value)}      />      <button        type="button"        onClick={() => setUrl(`${API_ENDPOINT}${query}`)}      >        Search      </button>
      <ul>        {data.hits.map((item) => (          <li key={item.objectID}>            <a href={item.url}>{item.title}</a>          </li>        ))}      </ul>    </>  );};
export default App;

接下来,你也将把数据存储在本地存储中。通过使用之前关于如何在React中使用本地存储的知识,我们可以用键/值对将结果存储到浏览器的存储中--而键是API端点的URL,值是实际结果。

const App = () => {  ...
  React.useEffect(() => {    const fetchData = async () => {      const result = await axios(url);
      localStorage.setItem(url, JSON.stringify(result));
      setData({ hits: result.data.hits });    };
    fetchData();  }, [url]);
  return (    ...  );};

最后一步使我们能够在用户每次对API进行搜索请求时将本地存储作为缓存。如果你搜索一个关键词,并且这个关键词的结果已经被保存(读作:缓存)在本地存储中,我们将从本地存储中读取,而不是执行另一个API调用。如果本地存储中没有结果,我们将执行通常的API请求。

const App = () => {  ...
  React.useEffect(() => {    const fetchData = async () => {      const cachedResult = JSON.parse(localStorage.getItem(url));
      let result;
      if (cachedResult) {        result = cachedResult;      } else {        result = await axios(url);        localStorage.setItem(url, JSON.stringify(result));      }
      setData({ hits: result.data.hits });    };
    fetchData();  }, [url]);
  return (    ...  );};

有了这个实现,就不应该为同一个查询做两次API请求,因为结果应该被缓存在本地存储中。如果在localStorage 实例中,有一个cachedResult ,那么缓存的结果就会被设置为状态,而不会执行API请求。不过要记住这一点,因为在现代的React数据获取库中,像React Query这样的库会为你处理好这样的缓存机制。