问题
在开发react-native
过程中,使用redux
保存状态迁移已基本成为一个标准做法。用户登录时的状态变更,会带来redux
状态迁移,而应用程序的其他部分也需要了解用户是否已登录以及相关的登录信息,只要软件不退出,通过reducer
我们总是能感知到变化的。但问题是软件退出后,reducer
从内存中消失,用户如果再次打开软件,还需要登录。简单做法是把登录的token等信息存储在react-native
提供的AsyncStorage
里,但这样一来就打断了和redux
的联系。有没有可能直接把redux
的信息保存在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
目录下的Store.js
文件中:
// @flow import { createStore,applyMiddleware,compose } from 'redux'; import { autoRehydrate } from 'redux-persist'; import createSagaMiddleware from 'redux-saga'; import rootReducer from '../Reducers/'; import sagas from '../Sagas/'; import RehydrationServices from '../Services/RehydrationServices'; import ReduxPersist from '../Config/ReduxPersist'; import Config from '../Config/DebugConfig'; // 屏蔽flow误报警 declare var console: any; // 添加saga中间件 let middleware = []; const sagaMiddleware = createSagaMiddleware(); middleware.push(sagaMiddleware); export default () => { let store = {}; // 根据配置要求采用Reactotron或者原生store const createAppropriateStore = Config.useReactotron ? console.tron.createStore : createStore; if (ReduxPersist.active) { // 如果配置中要求采用持久化 const enhancers = compose( applyMiddleware(...middleware),autoRehydrate() ); store = createAppropriateStore( rootReducer,enhancers ); // 启动持久化 RehydrationServices.updateReducers(store); } else { // 如果配置中不要求采用持久化 const enhancers = compose( applyMiddleware(...middleware),); store = createAppropriateStore( rootReducer,enhancers ); } // 运行saga sagaMiddleware.run(sagas); return store; };
代码中又对其他几段代码做了依赖,其中放在Reducers
目录下的index.js
中定义了黑名单,放在黑名单中的reducer
是不进行持久化的:
// @flow import { combineReducers } from 'redux'; import LoginReducer from './LoginReducer'; import ActivitiesReducer from './ActivitiesReducer'; import ActivityReducer from './ActivityReducer'; import ResourcesReducer from './ResourcesReducer'; import NewsesReducer from './NewsesReducer'; export default combineReducers({ login: LoginReducer,activities: ActivitiesReducer,activity: ActivityReducer,resources: ResourcesReducer,newses: NewsesReducer,}); // 添加persist黑名单,以下这些reducer不需要持久化 export const persistentStoreBlacklist = [ 'activities','activity','resources','newses',];
设置好黑名单之后,可以开始真正启用持久化了,这部分代码放在Services
目录下的RehydrationServices.js
里:
// @flow import { AsyncStorage } from 'react-native'; import { persistStore } from 'redux-persist'; import ReduxPersist from '../Config/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};
这里要取Config
目录下的ReduxPersist.js
文件的配置:
// @flow import { AsyncStorage } from 'react-native'; import immutablePersistenceTransform from '../Store/ImmutablePersistenceTransform'; import { persistentStoreBlacklist } from '../Reducers/'; const REDUX_PERSIST = { active: true,// 是否采用持久化策略 reducerVersion: '2',// reducer版本,如果版本不一致,将刷新整个持久化仓库 storeConfig: { storage: AsyncStorage,// 采用本地异步存储,react-native必须 blacklist: persistentStoreBlacklist,// 从根reducer获取黑名单,黑名单中的reducer不进行持久化保存 transforms: [immutablePersistenceTransform],// 重要,因为redux是immutable不可变的,此处必须将常规数据做变形,否则会失败 } }; export default REDUX_PERSIST;
这里用到了一个最重要的变形,否则整个过程不能成功,因为redux
里的对象都是immutable
不可变的,我们在将它们持久化的时候,必须转成mutable
可变的常规js对象,而从本地存储中取出来进入redux
循环的时候,又需要将它们变成immutable
的。下面这段代码要放在Store
目录下的ImmutablePersistenceTransform.js
中:
// @flow import R from 'ramda'; import Immutable from 'seamless-immutable'; // 将redux中的immutable对象转为普通js对象,以便于持久化存储 const isImmutable = R.has('asMutable'); const convertToJs = (state) => state.asMutable({deep: true}); const fromImmutable = R.when(isImmutable,convertToJs); // 将普通js对象转为immutable不可变,以供redux使用 const toImmutable = (raw) => Immutable(raw); export default { out: (state: any) => { // 设置深度合并 state.mergeDeep = R.identity; // 从仓库中取出,进入内存时,转为immutable不可变 return toImmutable(state); },in: (raw: any) => { // 进入仓库时,将immutable不可变数据转为常规数据 return fromImmutable(raw); } };
用法
和常规使用方法一样,原先如何使用redux
,现在还是怎么样用,应用程序启动时,直接判断保存用户登录信息的reducer
里有没有值就行了,如果没有的话,调出登录界面,如果有的话,直接从reducer
中取值。是不是很方便呢?
案例
完整代码可参见我在Github上的项目:Wecanmobile。觉得有帮助的话,请帮我打一颗星星。