在React Native中將Redux數據持久化

問題

在開發react-native過程當中,使用redux保存狀態遷移已基本成爲一個標準作法。用戶登陸時的狀態變動,會帶來redux狀態遷移,而應用程序的其餘部分也須要了解用戶是否已登陸以及相關的登陸信息,只要軟件不退出,經過reducer咱們老是能感知到變化的。但問題是軟件退出後,reducer從內存中消失,用戶若是再次打開軟件,還須要登陸。簡單作法是把登陸的token等信息存儲在react-native提供的AsyncStorage裏,但這樣一來就打斷了和redux的聯繫。有沒有可能直接把redux的信息保存在AsyncStorage裏呢?這樣一來咱們就既解決了記住用戶登陸信息的問題,同時又不打破redux的優良結構。react

Github上已經有現成的redux-persist包以解決redux持久化問題,但在實際使用過程當中,還有不少問題須要解決。具體來講,redux-persist這個包提供的是通用解決方案,也能夠用於react.js,若是你要用在react-native中的話,須要指定AsyncStorage,另外,雖然它還額外提供了兩個transform插件redux-persist-transform-immutableredux-persist-immutable,但這兩個插件目前使用起來仍是有問題沒有解決,爲了儘快用上redux-persist,能夠使用如下方案。git

解決

首先,在創建redux store時,除了常規會用到的各類中間件之外,咱們須要額外引入redux-persist裏的autoRehydrate加強器,而後啓動持久化。這部分代碼保存在Store目錄下的Store.js文件中:github

// @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是不進行持久化的:redux

// @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裏:react-native

// @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文件的配置:app

// @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中:less

// @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。以爲有幫助的話,請幫我打一顆星星。ide

相關文章
相關標籤/搜索