创见博客
React 实现状态持久化存储
七崽爱吃小饼干2026/01/17阅读 1专栏 React

React 实现状态持久化存储

在 React 项目中实现状态持久化,核心需求就是:页面刷新、浏览器重启、路由跳转后,React 组件的状态数据不会丢失,再次进入页面能恢复之前的状态,这也是 React 开发中高频的核心需求。

一、核心概念前置

React 中实现状态持久化,99% 的场景都是结合「浏览器本地存储」+「React 自身状态」实现的,核心逻辑:

  1. 持久化的存储载体:浏览器提供的 localStorage / sessionStorage(前端最常用、无侵入、无需额外依赖)
    • localStorage:永久存储(除非手动删除/代码清除/清除浏览器缓存),刷新/重启浏览器/关闭标签页都不会丢失,90% 业务首选此方案
    • sessionStorage:会话存储,只在「当前浏览器标签页」有效,标签页关闭则数据清空,适合临时存储、无需跨会话保留的状态
    • 两者用法完全一致,只是存储有效期不同,本文以 localStorage 为核心讲解,替换成 sessionStorage 无缝兼容
  2. 核心原理:状态双向同步
    • 初始化:组件挂载时,从 localStorage 中读取数据,赋值给 React 状态,实现「恢复状态」
    • 更新时:React 状态发生改变时,立即把最新的状态同步存入 localStorage,实现「持久化保存」

二、方案一:基础原生写法

适用场景

  • 项目简单、只有少量状态需要持久化(比如:用户信息、主题切换、表单草稿、分页页码等)
  • 不想引入额外第三方库,追求轻量化
  • 基于 React 函数组件(最主流的开发方式)+ useState

核心完整代码

jsx
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;

关键注意事项

  1. localStorage 存储规则:只能存储字符串类型!
    • 存「对象/数组」:必须用 JSON.stringify(数据) 转字符串
    • 取「对象/数组」:必须用 JSON.parse(字符串) 转回原类型
    • 存「字符串/数字/布尔值」:直接存,读取后可直接用
  2. useState 初始化用函数:useState(() => { ... }),而不是直接写逻辑。好处是:初始化逻辑只执行一次,避免组件每次重渲染都执行读取 localStorage 的操作,提升性能。
  3. useEffect 依赖项必加:监听的状态必须写在依赖数组中,确保状态一变就同步存储。

三、方案二:封装自定义 Hook

痛点分析

如果项目中有多个状态需要持久化,用方案一的话,每个状态都要写「初始化读取+useEffect同步」的重复代码,冗余且维护成本高。

解决思路

封装一个通用的自定义 Hook(比如叫 usePersistState),把「读取本地存储、状态同步存储」的逻辑全部封装起来,外部组件像用原生 useState 一样调用,一行代码实现持久化,彻底解耦逻辑。

核心:封装通用自定义 Hook usePersistState.js

jsx
// 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;

组件中使用

jsx
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;

优势

  1. 无感知使用:调用方式和原生 useState 几乎一致,学习成本为0;
  2. 代码极简:每个持久化状态只需要一行代码,告别重复逻辑;
  3. 完全解耦:持久化逻辑和业务组件分离,组件只关心业务,不关心存储;
  4. 通用性极强:支持任意类型的状态(对象、数组、字符串、数字等),支持所有函数组件。

四、方案三:类组件的状态持久化(兼容老项目)

适用场景

维护 React 类组件(Class Component)的老项目,没有 Hooks 语法,需要实现 state 持久化。

核心原理

类组件中没有 useState 和 useEffect,对应逻辑替换为:

  1. 初始化状态:在 constructor 构造函数中,从 localStorage 读取数据赋值给 this.state;
  2. 同步状态:监听状态变化 → 类组件中用 componentDidUpdate 生命周期钩子,状态更新后执行同步存储逻辑。

完整代码示例

jsx
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 库,核心作用:

  1. 自动将 Redux 的 store 数据同步到 localStorage/sessionStorage
  2. 页面刷新/项目重启时,自动从本地存储中读取数据并恢复到 Redux 的 store 中
  3. 支持按需持久化、白名单/黑名单配置、存储引擎切换,功能完善

前置说明

当前 React 生态中,Redux Toolkit (RTK) 是官方推荐的 Redux 开发方式,写法极简、内置中间件,99% 的新项目都用 RTK,所以下面提供两种完整写法:

  • 写法一:Redux Toolkit (RTK) + redux-persist(推荐,新项目必用)
  • 写法二:原生 Redux + redux-persist(兼容老项目)

写法一:Redux Toolkit + redux-persist 实现持久化(推荐)

步骤1:安装必需依赖

bash
# 核心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)

jsx
// 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)

jsx
// 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和使用状态(数据自动持久化)

jsx
// 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用法一致)

jsx
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持久化核心补充

  1. 切换存储引擎:如需将 Redux 数据存在 sessionStorage,只需替换 storage 引入即可
    jsx
    import storageSession from 'redux-persist/lib/storage/session';
    const persistConfig = { key: 'root', storage: storageSession };
    
  2. 按需持久化:项目中部分状态不需要持久化(比如临时筛选条件),用 blacklist/whitelist 精准控制,能有效减少本地存储体积,提升性能
  3. 清除持久化数据:退出登录时,需要清空 Redux 和本地存储的用户数据,直接调用内置方法即可
    jsx
    import { persistor } from './store';
    // 清空所有持久化数据
    persistor.purge();
    

总结

为了方便你快速选择适合自己项目的方案,整理了清晰的选型指南,按优先级排序:

优先级 1:封装自定义 Hook usePersistState

  • 适用:函数组件、中小型项目、少量/多个状态持久化
  • 优势:无依赖、极简、通用、解耦,项目最佳实践

优先级 2:原生 useState + useEffect 基础写法

  • 适用:入门学习、单个简单状态持久化、不想封装Hook的场景
  • 优势:零封装成本,容易理解

优先级 3:类组件 componentDidUpdate 写法

  • 适用:维护老项目、类组件场景
  • 优势:兼容所有类组件,无额外依赖

优先级 4:Redux + redux-persist 第三方库方案

  • 适用:中大型项目、全局状态持久化、多组件共享持久化状态
  • 优势:一站式解决全局状态管理+持久化,成熟稳定,生态完善

核心记住一个原则

React 状态持久化的本质就是:「React 内存状态」和「浏览器本地存储」的双向同步,所有方案都是围绕这个核心逻辑实现的,理解了这个本质,所有写法都能举一反三。

评论
0/100