- 使用RN也有一段时间了,但是却未试过在RN项目中实现数据的持久化,虽然项目继承了
react-redux
,但是数据存在于内存中,用户关闭APP后,需要重新初始化数据,网上大多RN数据持久化都是将一些数据存在AsyncStorage
中,但是却打断了与redux
的联系 Github
上已经有现成的redux-persist
包以解决redux
持久化问题,但在实际使用过程中,还有很多问题需要解决。具体来说,redux-persist
这个包提供的是通用解决方案,也可以用于react.js
,如果你要用在react-native
中的话,需要指定AsyncStorage
,另外,虽然它还额外提供了两个transform插件redux-persist-transform-immutable
和redux-persist-immutable
,但这两个插件目前使用起来还是有问题没有解决,为了尽快用上redux-persist
,可以使用以下方案。
解决
在建立redux store时,除了常规会用到的各种中间件以外,我们需要额外引入redux-persist里的autoRehydrate增强器,然后启动持久化。
Store.ts
import { applyMiddleware, createStore, compose } from 'redux';
import { autoRehydrate } from 'redux-persist';
import createSagaMiddleware from 'redux-saga';
// 中间件,作用:如果不使用该中间件,当我们dispatch一个action时,需要给dispatch函数传入action对象;
// 但如果我们使用了这个中间件,那么就可以传入一个函数,这个函数接收两个参数:dispatch和getState。这个dispatch可以在将来的异步请求完成后使用,对于异步action很有用
import thunk from 'redux-thunk';
import logger from 'redux-logger';
// 引入reducer
import reducer from './reducer';
// 引入持久化配置文件
import ReduxPersist from '../config/ReduxPersist';
// 引入版本变化重新持久化文件
import RehydrationServices from '../config/RehydrationServices';
// 使用redux-thunk中间件,处理异步action,这里则不实用saga中间件
// const sagaMiddleware = createSagaMiddleware();
// let middleware:any = [];
// middleware.push(sagaMiddleware);
let store: any = {};
const createAppropriateStore = createStore;
if (ReduxPersist.active) {
// 如果配置中要求采用持久化
const enhancers = compose(
applyMiddleware(thunk, logger), // 加入thunk中间件和日志中间件
autoRehydrate()
);
store = createAppropriateStore(
reducer,
enhancers
);
// 启动持久化
RehydrationServices.updateReducers(store);
} else {
// 如果配置中不要求采用持久化
store = createStore(
reducer,
applyMiddleware(thunk, logger)
);
}
export default store;
ReduxPersist.ts
持久化配置文件
import { AsyncStorage } from 'react-native';
import immutablePersistenceTransform from '../redux/ImmutablePersistenceTransform';
import { persistentStoreBlacklist } from '../redux/reducer';
const REDUX_PERSIST: any = {
active: true, // 是否采用持久化策略
reducerVersion: '1.0.0', // reducer版本,如果版本不一致,将刷新整个持久化仓库
storeConfig: {
storage: AsyncStorage, // 采用本地异步存储,react-native必须
blacklist: persistentStoreBlacklist, // 从根reducer获取黑名单,黑名单中的reducer不进行持久化保存
transforms: [immutablePersistenceTransform], // 重要,因为redux是immutable不可变的,此处必须将常规数据做变形,否则会失败
}
};
export default REDUX_PERSIST;
RehydrationServices.ts
判断是否替换持久化数据文件
import { AsyncStorage } from 'react-native';
import { persistStore } from 'redux-persist';
import ReduxPersist from './ReduxPersist';
const updateReducers = (store: any) => {
const reducerVersion = ReduxPersist.reducerVersion;
const config = ReduxPersist.storeConfig;
// 按照配置要求自动持久化reducer
persistStore(store, config);
AsyncStorage.getItem('reducerVersion').then((localVersion) => {
// 从本地存储取出reducer版本并比较
if (localVersion !== reducerVersion) {
// 如果本地存储中的reducer版本与配置文件中的reducer版本不同,则需要清理持久化数据
persistStore(store, config, () => {
persistStore(store, config);
}).purge();
// 清理成功,将本地存储中的reducer版本设为配置文件中的reducer版本
AsyncStorage.setItem('reducerVersion', reducerVersion);
}
}).catch(() => AsyncStorage.setItem('reducerVersion', reducerVersion));
}
export default {updateReducers};
reducer/index.ts
合并所有reducers和导出黑名单
// 工具函数,用于组织多个reducer,并返回reducer集合
import { combineReducers } from 'redux';
import configReducer from './configReducer';
import reduxPersister from './reduxPersister'
let reducers = {
config:configReducer,
reduxPersister,
};
// 导出所有reducer
export default combineReducers(reducers);
// 添加persist黑名单,以下这些reducer不需要持久化
export const persistentStoreBlacklist = [
'config',
];
ImmutablePersistenceTransform.ts
数据合并和转换文件
import R from 'ramda';
import Immutable from 'seamless-immutable';
// 将redux中的immutable对象转为普通js对象,以便于持久化存储
const isImmutable = R.has('asMutable');
const convertToJs = (state: any) => state.asMutable({deep: true});
const fromImmutable = R.when(isImmutable, convertToJs);
// 将普通js对象转为immutable不可变,以供redux使用
const toImmutable = (raw: any) => Immutable(raw);
export default {
out: (state: any) => {
// 设置深度合并
state.mergeDeep = R.identity;
// 从仓库中取出,进入内存时,转为immutable不可变
return toImmutable(state);
},
in: (raw: any) => {
// 进入仓库时,将immutable不可变数据转为常规数据
return fromImmutable(raw);
}
};
action.ts
异步action文件
import * as actionTypes from './actionTypes'
export function DemoAction(params:boolean) {
return (dispatch:any, getState:any) => {
dispatch({
type: actionTypes.SOMETEST,
data: params
});
};
};
configReducer.ts
import * as actionTypes from '../action/actionTypes';
const initialState = {
someData: ''
};
export default (state = initialState, action:any) => {
switch (action.type) {
case actionTypes.SOMETEST:
return {
...state,
someData: action.data
}
default:
return state
}
};