React 实现状态持久化存储
在 React 项目中实现状态持久化,核心需求就是:页面刷新、浏览器重启、路由跳转后,React 组件的状态数据不会丢失,再次进入页面能恢复之前的状态,这也是 React 开发中高频的核心需求。
一、核心概念前置
React 中实现状态持久化,99% 的场景都是结合「浏览器本地存储」+「React 自身状态」实现的,核心逻辑:
- 持久化的存储载体:浏览器提供的
localStorage/sessionStorage(前端最常用、无侵入、无需额外依赖)localStorage:永久存储(除非手动删除/代码清除/清除浏览器缓存),刷新/重启浏览器/关闭标签页都不会丢失,90% 业务首选此方案sessionStorage:会话存储,只在「当前浏览器标签页」有效,标签页关闭则数据清空,适合临时存储、无需跨会话保留的状态- 两者用法完全一致,只是存储有效期不同,本文以
localStorage为核心讲解,替换成sessionStorage无缝兼容
- 核心原理:状态双向同步
- 初始化:组件挂载时,从
localStorage中读取数据,赋值给 React 状态,实现「恢复状态」 - 更新时:React 状态发生改变时,立即把最新的状态同步存入
localStorage,实现「持久化保存」
- 初始化:组件挂载时,从
二、方案一:基础原生写法
适用场景
- 项目简单、只有少量状态需要持久化(比如:用户信息、主题切换、表单草稿、分页页码等)
- 不想引入额外第三方库,追求轻量化
- 基于 React 函数组件(最主流的开发方式)+
useState
核心完整代码
import { useState, useEffect } from 'react';
function PersistStateDemo() {
// 1. 初始化状态:优先从localStorage读取,没有则用默认值
const [userInfo, setUserInfo] = useState(() => {
// 从localStorage读取,key自定义(建议项目统一规范)
const storageValue = localStorage.getItem('user_info');
// 注意:localStorage只能存字符串,读取后要转成JSON对象,无数据则返回默认值
return storageValue ? JSON.parse(storageValue) : { name: '游客', token: '' };
});
const [theme, setTheme] = useState(() => {
// 简单类型(字符串/数字/布尔值)无需JSON转换,直接使用
return localStorage.getItem('app_theme') || 'light';
});
// 2. 监听状态变化,同步到localStorage(核心:状态更新 → 持久化存储)
useEffect(() => {
// 复杂类型(对象/数组)必须用JSON.stringify转成字符串存储
localStorage.setItem('user_info', JSON.stringify(userInfo));
}, [userInfo]); // 依赖项:只有userInfo变化时,才执行同步
useEffect(() => {
// 简单类型直接存,无需转换
localStorage.setItem('app_theme', theme);
}, [theme]);
// 测试:修改状态,刷新页面后状态不会丢失
const changeUser = () => setUserInfo({ name: '张三', token: 'abc123xyz' });
const toggleTheme = () => setTheme(theme === 'light' ? 'dark' : 'light');
return (
<div style={{ background: theme === 'dark' ? '#000' : '#fff', color: theme === 'dark' ? '#fff' : '#000', padding: 20 }}>
<div>当前用户:{userInfo.name}</div>
<div>用户Token:{userInfo.token}</div>
<button onClick={changeUser}>修改用户信息</button>
<button onClick={toggleTheme} style={{ marginLeft: 10 }}>切换主题</button>
</div>
);
}
export default PersistStateDemo;
关键注意事项
- localStorage 存储规则:只能存储字符串类型!
- 存「对象/数组」:必须用
JSON.stringify(数据)转字符串 - 取「对象/数组」:必须用
JSON.parse(字符串)转回原类型 - 存「字符串/数字/布尔值」:直接存,读取后可直接用
- 存「对象/数组」:必须用
- useState 初始化用函数:
useState(() => { ... }),而不是直接写逻辑。好处是:初始化逻辑只执行一次,避免组件每次重渲染都执行读取 localStorage 的操作,提升性能。 - useEffect 依赖项必加:监听的状态必须写在依赖数组中,确保状态一变就同步存储。
三、方案二:封装自定义 Hook
痛点分析
如果项目中有多个状态需要持久化,用方案一的话,每个状态都要写「初始化读取+useEffect同步」的重复代码,冗余且维护成本高。
解决思路
封装一个通用的自定义 Hook(比如叫 usePersistState),把「读取本地存储、状态同步存储」的逻辑全部封装起来,外部组件像用原生 useState 一样调用,一行代码实现持久化,彻底解耦逻辑。
核心:封装通用自定义 Hook usePersistState.js
// src/hooks/usePersistState.js
import { useState, useEffect } from 'react';
/**
* 持久化的useState Hook,用法和原生useState完全一致
* @param {any} initialValue - 状态默认值
* @param {string} key - localStorage的唯一标识key(必填,不能重复)
* @returns {[any, Function]} [状态值, 修改状态的方法]
*/
const usePersistState = (initialValue, key) => {
// 1. 初始化:从localStorage读取数据,无则用默认值
const [state, setState] = useState(() => {
const storageValue = localStorage.getItem(key);
return storageValue ? JSON.parse(storageValue) : initialValue;
});
// 2. 监听状态变化,自动同步到localStorage
useEffect(() => {
localStorage.setItem(key, JSON.stringify(state));
}, [state, key]);
// 返回和原生useState一致的格式:[状态, 修改状态的方法]
return [state, setState];
};
export default usePersistState;
组件中使用
import usePersistState from './hooks/usePersistState';
function AdvancedDemo() {
// 用法:和原生useState一样,只是多传一个唯一key即可
const [userInfo, setUserInfo] = usePersistState({ name: '游客', token: '' }, 'user_info');
const [theme, setTheme] = usePersistState('light', 'app_theme');
const [count, setCount] = usePersistState(0, 'count_num');
// 测试:修改状态,刷新页面数据不丢失
return (
<div>
<div>用户:{userInfo.name}</div>
<div>主题:{theme}</div>
<div>计数:{count}</div>
<button onClick={() => setUserInfo({ name: '李四', token: '999999' })}>修改用户</button>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>切换主题</button>
<button onClick={() => setCount(count + 1)}>计数+1</button>
</div>
);
}
export default AdvancedDemo;
优势
- 无感知使用:调用方式和原生
useState几乎一致,学习成本为0; - 代码极简:每个持久化状态只需要一行代码,告别重复逻辑;
- 完全解耦:持久化逻辑和业务组件分离,组件只关心业务,不关心存储;
- 通用性极强:支持任意类型的状态(对象、数组、字符串、数字等),支持所有函数组件。
四、方案三:类组件的状态持久化(兼容老项目)
适用场景
维护 React 类组件(Class Component)的老项目,没有 Hooks 语法,需要实现 state 持久化。
核心原理
类组件中没有 useState 和 useEffect,对应逻辑替换为:
- 初始化状态:在
constructor构造函数中,从 localStorage 读取数据赋值给this.state; - 同步状态:监听状态变化 → 类组件中用
componentDidUpdate生命周期钩子,状态更新后执行同步存储逻辑。
完整代码示例
import React, { Component } from 'react';
class ClassComponentPersist extends Component {
constructor(props) {
super(props);
// 1. 初始化:从localStorage读取数据赋值给state
this.state = {
userInfo: localStorage.getItem('class_user_info')
? JSON.parse(localStorage.getItem('class_user_info'))
: { name: '类组件游客', token: '' },
theme: localStorage.getItem('class_theme') || 'light'
};
}
// 2. 状态更新后同步到localStorage(核心生命周期)
componentDidUpdate(prevProps, prevState) {
// 判重:避免无意义的重复存储(性能优化)
if (prevState.userInfo !== this.state.userInfo) {
localStorage.setItem('class_user_info', JSON.stringify(this.state.userInfo));
}
if (prevState.theme !== this.state.theme) {
localStorage.setItem('class_theme', this.state.theme);
}
}
// 修改状态的方法
changeUser = () => {
this.setState({ userInfo: { name: '类组件张三', token: 'class_123' } });
};
toggleTheme = () => {
this.setState({ theme: this.state.theme === 'light' ? 'dark' : 'light' });
};
render() {
const { userInfo, theme } = this.state;
return (
<div style={{ background: theme === 'dark' ? '#000' : '#fff', color: theme === 'dark' ? '#fff' : '#000', padding: 20 }}>
<div>当前用户:{userInfo.name}</div>
<button onClick={this.changeUser}>修改用户</button>
<button onClick={this.toggleTheme} style={{ marginLeft: 10 }}>切换主题</button>
</div>
);
}
}
export default ClassComponentPersist;
五、补充:sessionStorage 与 localStorage 的切换
两者用法完全一致,只是存储有效期不同,所有方案都可以无缝切换,只需修改一个单词:
- 永久存储(推荐):
localStorage.getItem(key)/localStorage.setItem(key, value) - 会话存储(临时):
sessionStorage.getItem(key)/sessionStorage.setItem(key, value)
使用场景选择:
- 记住用户登录状态、主题偏好、用户配置、表单草稿 → 用
localStorage - 临时表单数据、页面临时筛选条件、不需要跨标签页保留的状态 → 用
sessionStorage
六、进阶方案:第三方库(Redux 完整持久化方案,大型项目首选)
适用场景
- 中大型 React 项目,存在大量全局共享状态需要持久化(比如:用户信息、全局主题、购物车、权限数据、菜单状态等)
- 项目中已经使用 Redux/Redux Toolkit 做全局状态管理,需要让 Redux 的 store 数据持久化,页面刷新后数据不丢失
核心依赖:redux-persist 核心库
Redux 本身的 store 数据是存储在内存中的,页面刷新就会清空,实现 Redux 持久化的官方主流方案就是使用 redux-persist 库,核心作用:
- 自动将 Redux 的 store 数据同步到 localStorage/sessionStorage
- 页面刷新/项目重启时,自动从本地存储中读取数据并恢复到 Redux 的 store 中
- 支持按需持久化、白名单/黑名单配置、存储引擎切换,功能完善
前置说明
当前 React 生态中,Redux Toolkit (RTK) 是官方推荐的 Redux 开发方式,写法极简、内置中间件,99% 的新项目都用 RTK,所以下面提供两种完整写法:
- 写法一:Redux Toolkit (RTK) + redux-persist(推荐,新项目必用)
- 写法二:原生 Redux + redux-persist(兼容老项目)
写法一:Redux Toolkit + redux-persist 实现持久化(推荐)
步骤1:安装必需依赖
# 核心redux依赖 + 持久化库
npm install @reduxjs/toolkit react-redux redux-persist
# yarn 安装
yarn add @reduxjs/toolkit react-redux redux-persist
步骤2:配置 Redux Store + 持久化(核心文件 src/store/index.js)
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; // 默认就是 localStorage
// import storageSession from 'redux-persist/lib/storage/session' // 如需用sessionStorage,引入这个
// 引入你的reducer(示例:用户模块)
import userReducer from './modules/userSlice';
import themeReducer from './modules/themeSlice';
// 1. 配置持久化规则
const persistConfig = {
key: 'root', // 本地存储的根key,自定义即可
storage: storage, // 存储引擎:localStorage
// blacklist: ['theme'] 可选:黑名单,指定不需要持久化的reducer
// whitelist: ['user'] 可选:白名单,只持久化指定的reducer(推荐,按需持久化更高效)
};
// 2. 包装reducer,注入持久化能力
const persistedReducer = persistReducer(persistConfig, {
user: userReducer,
theme: themeReducer
});
// 3. 创建store
export const store = configureStore({
reducer: persistedReducer,
// 必须配置,解决redux-persist的序列化警告
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
// 4. 创建持久化的store
export const persistor = persistStore(store);
步骤3:根组件注入持久化Provider(src/index.js)
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from './store';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
{/* PersistGate:等待持久化数据加载完成后再渲染页面,避免数据为空 */}
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
);
步骤4:正常编写Slice和使用状态(数据自动持久化)
// src/store/modules/userSlice.js
import { createSlice } from '@reduxjs/toolkit';
const userSlice = createSlice({
name: 'user',
initialState: {
name: '游客',
token: '',
avatar: ''
},
reducers: {
setUserInfo: (state, action) => {
state.name = action.payload.name;
state.token = action.payload.token;
},
clearUserInfo: (state) => {
state.name = '游客';
state.token = '';
}
}
});
export const { setUserInfo, clearUserInfo } = userSlice.actions;
export default userSlice.reducer;
组件中使用(无需关心持久化,和原生Redux用法一致)
import { useSelector, useDispatch } from 'react-redux';
import { setUserInfo } from './store/modules/userSlice';
function ReduxPersistDemo() {
const dispatch = useDispatch();
const userInfo = useSelector(state => state.user);
const updateUser = () => {
dispatch(setUserInfo({ name: 'Redux持久化用户', token: 'redux_123456' }));
};
return (
<div>
<div>用户名:{userInfo.name}</div>
<div>用户Token:{userInfo.token}</div>
<button onClick={updateUser}>修改用户信息</button>
</div>
);
}
export default ReduxPersistDemo;
Redux持久化核心补充
- 切换存储引擎:如需将 Redux 数据存在
sessionStorage,只需替换 storage 引入即可jsximport storageSession from 'redux-persist/lib/storage/session'; const persistConfig = { key: 'root', storage: storageSession }; - 按需持久化:项目中部分状态不需要持久化(比如临时筛选条件),用
blacklist/whitelist精准控制,能有效减少本地存储体积,提升性能 - 清除持久化数据:退出登录时,需要清空 Redux 和本地存储的用户数据,直接调用内置方法即可
jsx
import { persistor } from './store'; // 清空所有持久化数据 persistor.purge();
总结
为了方便你快速选择适合自己项目的方案,整理了清晰的选型指南,按优先级排序:
优先级 1:封装自定义 Hook usePersistState
- 适用:函数组件、中小型项目、少量/多个状态持久化
- 优势:无依赖、极简、通用、解耦,项目最佳实践
优先级 2:原生 useState + useEffect 基础写法
- 适用:入门学习、单个简单状态持久化、不想封装Hook的场景
- 优势:零封装成本,容易理解
优先级 3:类组件 componentDidUpdate 写法
- 适用:维护老项目、类组件场景
- 优势:兼容所有类组件,无额外依赖
优先级 4:Redux + redux-persist 第三方库方案
- 适用:中大型项目、全局状态持久化、多组件共享持久化状态
- 优势:一站式解决全局状态管理+持久化,成熟稳定,生态完善
核心记住一个原则
React 状态持久化的本质就是:「React 内存状态」和「浏览器本地存储」的双向同步,所有方案都是围绕这个核心逻辑实现的,理解了这个本质,所有写法都能举一反三。