Ant Design/UmiJS + DvaJS + redux-persist 实现数据持久化

2,025 阅读3分钟

具体实现

先上结论:在项目根目录下的 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 中,我们终于看到了他的详细配置包括:

  1. onError:管理全局出错状态
  2. onAction:在 action 被 dispatch 时触发,用于注册 redux 中间件
  3. onStateChange:state 改变时触发,可用于同步 state 到 localStorage,服务器端等
  4. onReducer:封装 reducer 执行
  5. onEffect:封装 effect 执行
  6. onHmr:热替换相关
  7. extraReducers:指定额外的 reducer
  8. extraEnhancers:指定额外的 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],
  },
};