4. Redux持久化中间件实现:redux-persist库配置

在SPA应用中,Redux Store默认存储在内存中,页面刷新后状态全部丢失。redux-persist是一个中间件库,它将Redux状态序列化到持久化存储(如localStorage、IndexedDB),并在应用启动时自动恢复,从而实现跨会话的状态保持。

核心思想:redux-persist通过拦截每次dispatch的action,在state更新后自动将指定的slice写入持久化引擎;应用加载时,先从存储中读取数据,用持久化状态合成为初始state,再触发rehydration完成恢复。

4.1 核心原理:持久化与恢复流程

下图展示了redux-persist的完整工作流程,从初始加载时的rehydration到每次状态变更后的自动持久化:

Redux Persist 持久化与恢复流程 Redux Persist 工作流程 应用启动 dispatch REHYDRATE PersistGate 读取持久化数据 localStorage / IndexedDB 返回持久化state 合并 state combineReducers + rehydrate Redux Store 完整state可用 用户操作 / Dispatch action → reducer persistReducer 中间件 白名单/黑名单过滤 写入存储引擎 序列化 → 持久化 白名单:只持久化指定key 黑名单:排除指定key

4.2 安装与基础配置

使用 redux-persist 需要安装核心库及所需的存储引擎适配器:

npm install redux-persist
# 如果使用 IndexedDB,额外安装:
npm install localforage   # 统一存储引擎
# 或
npm install idb-keyval    # 轻量级 IndexedDB 封装

基础配置流程分为 5 步:

  1. 导入 persistReducer 和 persistStore
  2. 配置 persistConfig 对象(key、storage、白名单/黑名单)
  3. 用 persistReducer 包裹 rootReducer
  4. 创建 store 时调用 persistStore
  5. 在组件树根节点使用 PersistGate(可选,但推荐)
标准配置模板(store.js):
import { createStore } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; // 默认 localStorage
import rootReducer from './reducers';

const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['user', 'cart'],      // 只持久化 user 和 cart
  blacklist: ['ui', 'temporary'],    // 排除 ui 和 temporary
};

const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer);
const persistor = persistStore(store);

export { store, persistor };

在应用的入口文件中使用 PersistGate:

import React from 'react';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from './store';
import App from './App';

const Root = () => (
  <Provider store={store}>
    <PersistGate loading={<div>Loading...</div>} persistor={persistor}>
      <App />
    </PersistGate>
  </Provider>
);

4.3 白名单与黑名单过滤

redux-persist 提供两种过滤机制,控制哪些 reducer key 被持久化:

过滤方式 配置属性 说明 适用场景
白名单 (whitelist) whitelist: string[] 仅持久化数组中列出的 reducer key 明确知道哪些状态需要跨会话保留
黑名单 (blacklist) blacklist: string[] 排除数组中列出的 reducer key,其余全部持久化 大多数状态都需要持久化,只排除敏感或临时数据
注意:whitelist 和 blacklist 不能同时使用。如果同时设置,redux-persist 会优先使用 whitelist,忽略 blacklist。推荐始终使用 whitelist 以减少不必要的写入操作。

配置示例(按模块划分持久化策略):

// 按模块拆分 persistConfig
const userPersistConfig = {
  key: 'user',
  storage,
  whitelist: ['profile', 'preferences'],  // 只持久化 profile 和 preferences
};

const cartPersistConfig = {
  key: 'cart',
  storage,
  blacklist: ['couponCodes'],  // 不持久化优惠券码
};

// 每个 reducer 独立包裹
const userPersistReducer = persistReducer(userPersistConfig, userReducer);
const cartPersistReducer = persistReducer(cartPersistConfig, cartReducer);

4.4 存储引擎切换

redux-persist 通过 storage 属性解耦持久化实现。官方和社区提供了多种存储引擎适配器:

存储引擎 导入路径 容量限制 同步/异步 适用场景
localStorage redux-persist/lib/storage ~5-10MB (浏览器限制) 同步 小型应用、少量配置数据
sessionStorage redux-persist/lib/storage/session 同 localStorage 同步 会话级数据,关闭标签页即清除
IndexedDB (localForage) redux-persist/lib/storage 需自行创建 ~50MB+ (通常为磁盘可用空间) 异步 大量数据、离线优先应用
IndexedDB (idb-keyval) redux-persist-indexeddb-storage ~50MB+ 异步 需要精细控制 IndexedDB 操作

切换为 IndexedDB(使用 localForage):

import { createStore } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import localforage from 'localforage';
import rootReducer from './reducers';

// 自定义 IndexedDB 配置
localforage.config({
  name: 'MyAppStore',
  storeName: 'redux_persist',
  driver: localforage.INDEXEDDB,   // 强制使用 IndexedDB
  version: 1.0,
});

const persistConfig = {
  key: 'root',
  storage: localforage,   // 传入 localforage 实例作为存储引擎
  whitelist: ['user', 'cart', 'orders'],
};

const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer);
const persistor = persistStore(store);

切换为 sessionStorage:

import storage from 'redux-persist/lib/storage/session';

const persistConfig = {
  key: 'root',
  storage,   // sessionStorage 实例
};

4.5 高级配置:序列化与转换器

redux-persist 默认使用 JSON.stringify/parse 进行序列化。若需处理 Date、Map、Set 等特殊类型,可自定义 serialize/deserialize 或使用 transform:

import { createTransform } from 'redux-persist';

// 示例:将 Date 对象转为 ISO 字符串后再存储
const dateTransform = createTransform(
  // 出库:写入存储前
  (inboundState, key) => {
    if (key === 'user') {
      return {
        ...inboundState,
        lastLogin: inboundState.lastLogin instanceof Date
          ? inboundState.lastLogin.toISOString()
          : inboundState.lastLogin,
      };
    }
    return inboundState;
  },
  // 入库:从存储读取后
  (outboundState, key) => {
    if (key === 'user' && typeof outboundState.lastLogin === 'string') {
      return {
        ...outboundState,
        lastLogin: new Date(outboundState.lastLogin),
      };
    }
    return outboundState;
  },
  { whitelist: ['user'] }
);

const persistConfig = {
  key: 'root',
  storage,
  transforms: [dateTransform],
};

4.6 常见错误与调试

错误表现 原因 解决方案
rehydration 后 UI 闪白 PersistGate 未等待 rehydration 完成 使用 PersistGate 包裹根组件,并设置 loading 状态
存储数据未恢复 persistConfig 中 key 与之前配置不一致 保持 key 不变,否则会创建新的存储键
写入存储报 QuotaExceededError localStorage 空间不足 切换为 IndexedDB;减少白名单内容;压缩 state
异步存储导致 dispatch 时序错误 rehydration 未完成就发起业务请求 在 PersistGate 的 onBeforeLift 中初始化数据
黑名单配置后数据仍然被持久化 whitelist 与 blacklist 同时存在且 whitelist 优先 只使用一种过滤方式,推荐 whitelist
调试技巧:在 Chrome DevTools Application 面板中查看 localStorage 或 IndexedDB 中 persist:<key> 键名的数据。若需手动清除持久化状态,执行 persistor.purge()。

4.7 性能优化建议

  • 缩小持久化范围:只持久化必要的 slice(使用 whitelist),避免存储大型临时数据。
  • 使用 IndexedDB:对于超过 5MB 的状态,优先选择 IndexedDB 存储引擎,避免 localStorage 的同步写入阻塞主线程。
  • 节流写入:在 reducer 中减少不必要的 dispatch,或使用 throttle 包装 persistReducer 的写入:
import { createStore } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import throttle from 'lodash/throttle';

const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['user', 'settings'],
  throttle: 1000,   // 每 1 秒最多写入一次
};

const store = createStore(persistReducer(persistConfig, rootReducer));
const persistor = persistStore(store);
  • 分批 rehydration:使用 persistConfig.timeout 设置超时时间(默认 5000ms),避免应用等待过久。
const persistConfig = {
  key: 'root',
  storage,
  timeout: 3000,   // 3秒超时,超时后放弃加载持久化数据
};

4.8 配置清单

配置 redux-persist 完成后,可用下方清单检查完整性:

检查项 是否完成 备注
安装 redux-persist 依赖 ☐ npm install redux-persist
配置 persistConfig 并指定 storage ☐ 默认 localStorage,可切换 IndexedDB
用 persistReducer 包裹 rootReducer ☐ 传入 persistConfig
调用 persistStore 创建 persistor ☐ 导出供 PersistGate 使用
在组件树根节点添加 PersistGate ☐ 设置 loading 状态
配置 whitelist 或 blacklist ☐ 推荐 whitelist
验证 rehydration 后数据正确 ☐ 检查 Application 面板