redux-persist V5

作者: JamesSawyer | 来源:发表于2018-03-26 11:27 被阅读826次

    在创建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-persist V5

        本文链接:https://www.haomeiwen.com/subject/ilalcftx.html