redux-persist V5

96
JamesSawyer
0.6 2018.03.26 11:27* 字数 1337

在创建redux store 时, 将persistReducer包装应用的rootReducer,然后传递给createStore函数。一旦store创建完成,将其传递给persistStore函数,用来确保redux状态当发生变化时能够持久化存储

// src/store/index.js
import {createStore} from 'redux';
import {persistStore, persistReducer} from 'redux-persist';
import storage from 'reduc-persist/lib/storage';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import rootReducer from './reducers'; // 即combineReducers之后的rootReducer

const persistConfig = {
  key: 'root',
  storage: storage,
  stateReconciler: autoMergeLevel2 // 查看 'Merge Process' 部分的具体情况
};

const persistReducer = persistReducer(persistConfig, rootReducer);  // 包装rootReducer
export const store = createStore(persistReducer);     // 传递给createStore函数 这个export
export const persistor = persistStore(store);  // 包装store 这个也export

如果使用React,则使用 PersistGate 包裹根组建。这将延迟渲染app UI直到持久化状态取回并保存到redux中

import React from 'react';
import {Provider} from 'react-redux';
import { PersistGate } from 'redux-persist/lib/integration/react';
// 下面2种引入方式也可以
// import { PersistGate } from 'redux-persist/lib/integration/react';
// import { PersistGate } from 'redux-persist/integration/react';

// import 
import {store, persistStore} from './store';

import {RootComponent, LoadingView} from './components';

const App = () => {
    return (
        <Provider store={store}>
            // loading 和 persistor是2个必需属性
            // loading={null} || loading={<LoadingView />} LoadingView为React组件
            // 最好将loading={null},写成loading={<LoadingView />} 报错,原因暂不明
            <PersistGate loading={null} persistor={persistor}>
                <RootComponent />
            </PersistGate>
        </Provider>
    )
}
export default App;

自定义存储内容 (Customizing what's Persisted)

如果不想将部分state持久化,可以将其放入黑名单(blacklist)中.黑名单是设置PersistReducer时传入的配置对象

const persistConfig = {
  key: 'root',
  storage: storage,
  blacklist: ['navigation']
};

const persistReducer = persistReducer(persistConfig, rootReducer);

export const store = createStore(persistReducer);
export const persistor = persistStore(store);

黑名单接收字符串数组。每个字符串必须匹配部分状态(传入persistReducer中reducer管理的状态)。上面的示例,如果 rootReducer通过createReducers创建,我们期望 navigation 像下面一样存在在reducer中:

combineReducers({
  auth: AuthReducer,
  navigation: NavReducer, // navigation即reducer中的状态
  notes: NotesReducer
})

白名单(whitelist)的设置和黑名单一样,除了它表示的是你想要持久化的state\

const persistConfig = {
  key: 'root',
  storage: storage,  // 或者是 AsyncStorage for React Native
  whitelist: ['auth', 'notes']
}

假如你想要将一个嵌套的属性加入黑名单怎么办?例如,假设你的state对象有一个auth key,你想将auth.currentUser持久化,而不持久化auth.isLoggingIn

为了完成这个任务,使用PersistReducer包裹AuthReducer,然后将isLoggingIn加入黑名单。这将允许共同定位持久化规则和它依附的reducer

// AuthReducer.js
import storage from 'reduc-persist/lib/storage';
import {persistReducer} from 'redux-persist';

const INITIAL_STATE = {
  currentUser: null,
  isLoggingIn: false
};

const AuthReducer = (state = INITIAL_STATE, action) => {
  // reducer 实现。。。
}

const persistConfig = {
  key: 'auth',
  storage: storage,
  blacklist: ['isLoggingIn']
};

export default AuthReducer;

如果你更喜欢将所有的持久化规则都放在一起,而不是单独放在各自的reducer中,可以考虑将其放在combineReducers函数中:

// src/reducers/index.js
import {combineReducers} from 'redux';
import storage from 'redux-persist/lib/storage';
import {persistReducer} from 'redux-persist';

import {authReducer, navReducer, notesReducer} from './reducers';

const rootRersistConfig = {
  key: 'root',
  storage: storage,
  blacklist: ['navigation']
}
const authPersistConfig = {
  key: 'auth',
  storage: storage,
  blacklist: ['isLoggingIn']
}

const rootReducer = combineReducers({
  auth: persistReducer(authPersistConfig, authReducer),
  navigation: navReducer,
  notes: notesReducer
});

export default persistReducer(rootPersistConfig, rootReducer);

合并过程

当应用启动时, redux设置一个初始状态,这之后,redux persist从storage中取回你持久化的state。然后取回的持久化state将覆盖初始的state

合并过程是自动工作的,但是你也可以手动的处理这个过程。比如,以前redux-persist版本中通常通过捕获reducers中REHYDRATE动作来管理rehydration过程,然后将action的payload存储在redux的状态中

import {REHYDRATE} from 'redux-persist';

const INITIAL_STATE = {
    currentUser: null,
    isLoggingIn: false
}

const AuthReducer = (state = INITIAL_STATE, action) => {
    switch (action.type) {
      case REHYDRATE:
        return {
            ...state,
            currentUser: action.payload.currentUser
        };
      // ...其它的情况
    }
}

REHYDRATE动作在persisted state从storage中获取之后立即通过redux-persist发送出去。如果你从REHYDRATE返回一个新的state 对象,这将是你最终的状态。就如上面所说的,现在再也不需要这样做了,除非你需要自定义状态rehydrated的方式

注意下面一些陷阱

这个陷阱来自合并过程,这和合并过程在state对变更的深入程度有关, 上面提到合并过程将覆盖你的初始状态,无论你持久化了什么。下面是默认的工作方式:

// 假设初始状态如下,并且将整个状态都持久化
// initial state
{
  auth: {
    currentUser: null,
    isLoggingIn: false
  },
  notes: []
}

// 一旦应用启动
// 持久化状态
{
  auth: {
    currentUser: {firstName: 'Mark', lastName: 'Newtorn'},
    isLoggingIn: false
  },
  notes: [noteA, noteB, noteC]
}

默认情况,合并过程简单的替换每个顶层的state,和下面方式类似:

const finialState = {...initialState};
finialState['auth'] = persisedState['auth'];
finialState['notes'] = persisedState['notes'];

这通常没什么问题,但是假如你想发布一个新版本的app,并且将初始状态设置如下:

const INITIAL_STATE = {
  currentUser: null,
  isLoggingIn: false,
  error: ''
}   

很明显你想在最终state中包含新的 error key.但是持久化状态对象中不存在这个error key, 它将在rehydration过程中完全的替换你的初始状态,因此error key不会添加进去。

解决办法是告诉 PersistReducer合并 two-level 深度。在最上面,你可能在root PersistReducerzhong 注意到了神秘的 stateReconciler 设置

import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
const persistConfig = {
 key: 'root',
 storage: storage,
 stateReconciler: autoMergeLevel2  // 神秘的stateReconciler
};

autoMergeLevel2 就是告诉 PersistReducer合并 two-level 深度。对auth 状态,这意味着合并过程首先复制一份初始的auth state,然后只覆盖auth对象中持久化的keys.由于 error 还没有持久化,因此不会被丢弃。

重要的是,我们需要知道PersistReducers默认是 autoMergeLevel1,这表示替换持久化顶层的状态。如果你没有一个单独的PersistReducer管理持顶层keys的持久化状态,你可能需要使用 autoMergeLevel2.

另外,redux-persist的作者意识到,选择 autoMergeLevel1还是 autoMergeLevel2让人感到困惑,因此他创建了一个叫 persistCombineReduers的函数来简化这个过程。这个函数的实现只有2行代码,就是简单的给PersitReducer返回autoMergeLevel2,我个人偏好是自己去管理level,而不是用这个函数。当然这取决于你自己。

高级自定义

Transforms

转换允许你自定义持久化和rehydrated 的state object。

当state对象被持久化,它首先使用 JSON.stringify() 序列化state.如果你的部分state对象不能映射为JSON对象,则序列化过程可能出错。例如,js Set 数据类型在JSON中不存在,但你试着通过上面的方式序列化时,Set类型的数据将转换为一个空的对象。这可能不是你所期望的。

下面是一个成功转换Set数据类型的转换(transform),它简单的将其转换为一个数组然后使用时再转换回来。

import {createTransform} from 'redux-persist';

const SetTransform = createTransform(
  // 在state被序列化和持久化的过程中进行转换
  (inboundState, key) => {
    // 将set转换为array
    return {...inboundSate, mySet: [...inboundState.mySet]};
  },
  // rehydrated的时候转换
  (outboundState, key) => {
      return {...outboundState, mySet: new Set(outboundState.mySet)}
  },
  // 定义需要被转换的reducer
  {whitelist: ['someReducer']}
)
export default SetTransform;

createTransform函数接收3个参数:

  1. state持久化之前调用的函数
  2. 持久化数据转变为state前调用的函数(也称之为 rehydrated)
  3. 一个配置对象

现在我们将transforms添加到 PersistReducer的配置对象中:

import storage from 'redux-persist/lib/storage';
import { SetTransform } from './Transforms';

const persistConfig = {
  key: 'root',
  storage: storage,
  transform: [SetTransform]  // 添加转换函数
}

// ...

文章来源:

相关文章:

Redux
Web note ad 1