具体实现
先上结论:在项目根目录下的 src/app.tsx 添加如下代码
import {getDvaApp, RunTimeLayoutConfig } from 'umi';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { Reducer, StoreEnhancer } from 'redux';
// 这个是redux-persist 的配置
const persistConfig = {
key: 'root', // 自动框架生产的根目录id 是root。不变
storage, // 这个是选择用什么存储,session 还是 storage
};
const persistEnhancer:StoreEnhancer = (next) =>
(reducer:Reducer<any, any>) => {
const store = next(persistReducer(persistConfig, reducer));
const persist = persistStore(store) ;
return { ...store, persist };
}
export const dva = {
config: {
extraEnhancers: [persistEnhancer],
},
};
//在需要的地方 如 getInitialState 初始化阶段 加入下面代码
{
const app = getDvaApp()
persistStore(app._store)
}
探索过程
redux-persist
状态持久化我首先想到的是 redux 的 redux-persist 插件,找到 redux-persist 项目地址,可以看到他给我们的配置实例:
// configureStore.js
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web
import rootReducer from './reducers'
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
export default () => {
let store = createStore(persistedReducer)
let persistor = persistStore(store)
return { store, persistor }
}
但我们的项目是基于UmiJS开发的,而UmiJS官方推荐的状态管理用的是DvaJS并不能像传统的redux方案一样直接用 persistedReducer 包装reducers。
UmiJS - @umijs/plugin-dva
为能在UmiJS 中使用 redux-persist 插件, 我翻阅了UmiJS文档关于dva的配置,通过 src/app.tsx 文件配置 dva 创建时的参数。
比如:
import { createLogger } from 'redux-logger';
import { message } from 'antd';
export const dva = {
config: {
onAction: createLogger(),
onError(e: Error) {
message.error(e.message, 3);
},
},
};
但文档中并未列出具体的config配置项用法,如要更深入的配置我们需要找到DvaJS的详细配置文档
DvaJS/API#app-use-hooks
在DvaJS API文档的app-use-hooks 中,我们终于看到了他的详细配置包括:
onError:管理全局出错状态onAction:在 action 被 dispatch 时触发,用于注册 redux 中间件onStateChange:state 改变时触发,可用于同步 state 到 localStorage,服务器端等onReducer:封装 reducer 执行onEffect:封装 effect 执行onHmr:热替换相关extraReducers:指定额外的 reducerextraEnhancers:指定额外的 StoreEnhance
那么多的配置我们应该用哪一个呢?
还记得我们之前缺少的reducers吗,正好onReducer可以帮助我们获取并返回加强后的reducer。
方案1:onReducer
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
const app = dva({
onReducer: reducer => {
return (state, action) => {
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, reducer)
let store = createStore(persistedReducer)
let persistor = persistStore(store)
return { store, persistor }
},
},
});
方案2:extraEnhancers
本质上 redux-persist 是一个 StoreEnhancer
Store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。这与 middleware 相似,它也允许你通过复合函数改变 store 接口。
Store enhancer 与 React 的高阶 component 概念一致,通常也会称为 “component enhancers”。
因为 store 并非实例,更像是一个函数集合的普通对象,所以可以轻松地创建副本,也可以在不改变原先的 store 的条件下修改副本。
所以更好的方法是写在extraEnhancers里,就如DvaJS在文档中的例子一样:
import { persistStore, autoRehydrate } from 'redux-persist';
const app = dva({
extraEnhancers: [autoRehydrate()],
});
persistStore(app._store);
就在我根据官网例子写的引入了autoRehydrate时,autoRehydrate导入失败了。在翻看官方文档的时候发现autoRehydrate在 redux-persis v5版本后以及不存在了,只能自己写了。
autoRehydrate 实现
根据文档我们知道这个函数返回的是一个 StoreEnhancer:
export type StoreEnhancer<Ext = {}, StateExt = {}> = (
next: StoreEnhancerStoreCreator
) => StoreEnhancerStoreCreator<Ext, StateExt>
export type StoreEnhancerStoreCreator<Ext = {}, StateExt = {}> = <
S = any,
A extends Action = AnyAction
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => Store<S & StateExt, A> & Ext
从中我们可以看出 StoreEnhancer 是一个函数 ,它接受一个类型为StoreEnhancerStoreCreator的next参数,同时返回值也是相同类型。那这个 StoreEnhancerStoreCreator 又是啥呢,其实就是 StoreCreator增强类型,由于只是Store数据上的增强器为了不对action修改,约束了其Action保持前后一致。
其中StoreEnhancerStoreCreator有我们需要的reducer,那么方案2的写法就出来了:
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { Reducer, StoreEnhancer } from 'redux';
// 这个是redux-persist 的配置
const persistConfig = {
key: 'root', // 自动框架生产的根目录id 是root。不变
storage, // 这个是选择用什么存储,session 还是 storage
};
const persistEnhancer:StoreEnhancer = (next) =>
(reducer:Reducer<any, any>) => {
const store = next(persistReducer(persistConfig, reducer));
const persist = persistStore(store) ;
return { ...store, persist };
}
export const dva = {
config: {
extraEnhancers: [persistEnhancer],
},
};