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到每次状态变更后的自动持久化:
4.2 安装与基础配置
使用 redux-persist 需要安装核心库及所需的存储引擎适配器:
npm install redux-persist
# 如果使用 IndexedDB,额外安装:
npm install localforage # 统一存储引擎
# 或
npm install idb-keyval # 轻量级 IndexedDB 封装
基础配置流程分为 5 步:
- 导入 persistReducer 和 persistStore
- 配置 persistConfig 对象(key、storage、白名单/黑名单)
- 用 persistReducer 包裹 rootReducer
- 创建 store 时调用 persistStore
- 在组件树根节点使用 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 面板 |