在React Native中将Redux数据持久化

问题

在开发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-immutableredux-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。觉得有帮助的话,请帮我打一颗星星。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom