经常在面试贴中看到这样一个问题:如何在页面刷新之后保持状态?
在React中,我们通常用useState来处理组件的状态,一旦页面刷新,组件卸载后再重新渲染,状态就会被重置,因为这种状态只保存内存中。但在有些场景下,我们希望在页面刷新之后,依然可以保存页面的状态。比如下面的代码示例:
import { useEffect, useState } from “react”;
export default function App() {
const [count, setCount] = useState(0);
const increaseCount = () => {
return setCount(count + 1);
}
const decreaseCount = () => {
return setCount(count — 1)
}
return (
<div>
<h1> Count {count} </h1>
<button onClick={increaseCount}>+</button>
<button onClick={decreaseCount}>-</button>
</div>
);
}
我们可以通过状态持久化的方案来决解这个问题,简单就说就是把状态缓存起来,需要用的时候再拿出来用,麻烦的是需要自己管理状态的缓存。
下面介绍几种常用的状态缓存的方式。
1. 浏览器缓存
我们熟知的比较常用的就是localStorage和sessionStorage这两个,具体用法这里就不介绍了,二者的主要区别在于生命周期,localStorage不删除不会消失,sessionStorage只在一个浏览器会话内有效,关闭浏览器tab自动删除。
为了使用方便,我们可以写个hook来使用,下面以locastorage为例:
export const useLocalStorage = (name) => {
const getLocalStorage = () => {
const local = localStorage.getItem(name)
if(local != null){
return JSON.parse(local)
}
return null
}
const setLocalStorage = (item) => {
localStorage.setItem(name, JSON.stringify(item))
}
const removeLocalStorage = () => {
return localStorage.removeItem(name)
}
return [getLocalStorage, setLocalStorage, removeLocalStorage]
}
- getLocalStorage:读取本地存储的状态
- setLocalStorage:将状态存储到本地
- removeLocalStorage:从本地存储中删除状态
使用示例:
import { useEffect, useState } from "react";
import { useLocalStorage } from "./utils/hooks";
let initialForm = {
name: "",
website: "",
contact: {
cell: "",
email: "",
},
};
const App = () => {
const [savedForm, setSavedForm, clearLocalStorage] =
useLocalStorage("inputForm");
const [inputFormState, setInputFormState] = useState(
savedForm() || initialForm
);
const handleFormChange = (event) => {
const { name, value } = event.target;
if (name === "name" || name === "website") {
setInputFormState((prev) => {
const newForm = { ...prev };
newForm[name] = value;
return newForm;
});
}
if (name === "cell" || name === "email") {
setInputFormState((prev) => {
let newForm = { ...prev };
newForm.contact[name] = value;
return newForm;
});
}
};
useEffect(() => {
setSavedForm(inputFormState);
}, [setSavedForm, inputFormState]);
return (
<>
<div>
Name:
<input
name="name"
value={inputFormState?.name}
onChange={(e) => handleFormChange(e)}
/>
</div>
<div>
Website:
<input
name="website"
value={inputFormState?.website}
onChange={(e) => handleFormChange(e)}
/>
</div>
<div>
Cell:
<input
name="cell"
value={inputFormState?.contact?.cell}
onChange={(e) => handleFormChange(e)}
/>
</div>
<div>
Email:
<input
name="email"
value={inputFormState?.contact?.email}
onChange={(e) => handleFormChange(e)}
/>
</div>
<div>
<button onClick={() => clearLocalStorage()}>Clear Cache</button>
</div>
</>
);
};
export default App;
sessionStorage同理。
2.history.state
histroy.state 可以保存我们的应用状态,与某一个访问记录绑定,页面跳转后就访问不到前一个页面的state,返回前一页面后可继续访问,需要使用 pushState 和 replaceState来修改history,用法及使用场景参考:history的pushState和replaceState用法及适用场景
3. URL
将状态保存到浏览器 URL 中,当我们初始化组件时,会从 URL 参数中读取初始值。
PS:由于 URL 长度限制,对于比较简单的数据此方法适用。
import { useEffect, useState } from "react";
import "./styles.css";
import qs from "qs";
import { createBrowserHistory } from "history";
export default function App() {
const [count, setCount] = useState(0);
const history = createBrowserHistory();
useEffect(() => {
const filterParams = history.location.search.substr(1);
const filtersFromParams = qs.parse(filterParams);
if (filtersFromParams.count) {
setCount(Number(filtersFromParams.count));
}
}, []);
useEffect(() => {
history.push(`?count=${count}`);
}, [count]);
const increaseCount = () => {
return setCount(count + 1);
}
const decreaseCount = () => {
return setCount(count - 1)
}
return (
<div className="App">
<h1> Count {count} </h1>
<button onClick={increaseCount}>+</button>
<button onClick={decreaseCount}>-</button>
</div>
);
}
4. 其他库
下面列举一些可用的库,使用方法可自行搜索对应库文档,这里就不做赘述了。
-
@reduxjs/toolkit 结合 Redux-persist
-
recoil + recoil-persist
-
zustand(自带persist)