为什么要用 redux-persist

12,268 阅读4分钟
原文链接: mp.weixin.qq.com

现代的应用包括 SPA ,原生 App 都对状态持久化有强烈的需求,浏览器提供了 LocalStorage 、IndexedDB 等持久化方案和标准,React Native 提供了 AsyncStorage 都是用来解决这些问题。

我们使用这些 API,我们的代码可能类似这样:

 registerUser: function (user, success, fail) {
      API.registerUser(user, function(userServer){
        var localUser = UserLocalStorage.serverUserToLocalUser(userServer);

        UserLocalStorage.save(localUser);
        this.user = localUser;
        if(success){
          success();
        }
      }.bind(this), fail);

  }

在注册用户时,需要保持当前的用户信息到本地存储中。

if (!this.user) {
      this.user = {};
      UserLocalStorage.get(function (user) {
        this.user = user;
      }.bind(this));
    }

当进入需要登录才能访问的页面检查 user 是否存在,如果不存在优先在本地存储中获取。如果只有一两个状态需要持久化,我们可以编写类似的代码,如果存在大量的状态需要持久化呢?

引入 redux-persist

import {persistStore, autoRehydrate} from 'redux-persist'
const store = createStore(reducer, autoRehydrate())
persistStore(store)

最简单的方式就是这三行代码,我们看看这三行代码可以帮助我们完成哪些事情?

persistStore(store, [config, callback])

  • arguments

    • blacklist 黑名单数组,可以忽略一些 reducers 中的 key。

    • whitelist 白名单数组,一旦设置,其他的 key 都会被忽略。

    • storage 一个 Engine,例如 LocalStorage 和 AsyncStorage

    • transforms 在 rehydration 和 storage 阶段被调用的转换器

    • debounce storage 操作被调用的频度。

    • store redux store 我们要存储的 store。

    • config 对象

  • callback rehydration 操作结束后的回调。

  • returns 返回值是 persistor 对象,可以调用后续的链式方法。

这个方法主要帮助我们做了两件事情,生成存储 store 的 persistor 和 rehydration action 的 dispatch,配合 autoRehydrate这个增强器(enhancer) 就完成了我们前面自己手写的代码,如果我们 store 中所有的数据都需要持久化并且没有什么特殊的需求,简单的两行代码就可以解决问题了。

autoRehydrate 的增强器是什么呢?其实也很简单,如果没有特殊的 merge 逻辑,它就用 storage 中的数据去恢复(rehydrate) store 中的数据,就可以用 autoRehydrate 了,他会做些简单的合并逻辑,而且如果 state 被 reducer 改变了,他也会「聪明的」用改动后的值替换掉 storage 中原来的值。

更多使用姿势

如果需要对恢复 Storage 到 store 有更多细节的控制怎么办?

我们可以去掉 autoRehydrate() 的调用,采用手动恢复的方式:

import {REHYDRATE} from 'redux-persist/constants'
//...
case REHYDRATE:
  var incoming = action.payload.myReducer
  if (incoming) return {...state, ...incoming, specialKey: processSpecial(incoming.specialKey)}
  return state

一般放到对应的 reducer.js 中,当收到 REHYDRATE action 时,通过自定义的逻辑来 merge 出最终的 store,比如我记录用户访问的路径而不是每次都替换掉他。

如果恢复 store 时需要导入额外的数据?或者每次恢复后删除某些或全部在 storage 中的数据?

前面我们提到 persistStore 的返回值是 persistor 对象,他有如下方法可以解决这个问题: persistor

  • .purge(keys)

    • keys 从 Storage 中删除指定的 key,如果不指定就清空。

  • .rehydrate(incoming, options)

    • 合并 incoming 传入的数据到 store 中。

    • options 如果设置了 serial:true ,incoming 是个字符串,会被反序列化后传入到 store 中。

了解更多细节

深入细节会发现 redux-persist 设计非常简单,很好的利用了 redux 提供的中间件管道特性来解决这个面向切面的问题域。

persistStore 从外部指定的 StorageEngine 中获取到所有数据,并进行反序列化和转换,并通过 dispatch REHYDRATE action,触发 REHYDRATE 过程,如果用户自定义了 rehydrate 逻辑则优先触发,如果配置了 autoRehydrate() ,则会恢复用户没有明确指定的特殊逻辑的 key。

persistor 会订阅 redux store,一旦发生变化就触发存储操作,可以通过指定 debounce 来优化性能,指定存储操作调用的频度。

通过配合用户自定义的 transforms 用户可以再次干预存储和恢复的过程,例如采用自定义的序列化反序列化方式:

import { createTransform, persistStore } from 'redux-persist'

let myTransform = createTransform(
  (inboundState, key) => specialSerialize(inboundState, key),
  (outboundState, key) => specialDeserialize(outboundState, key),
  {whitelist: ['specialReducer']}
)

persistStore(store, {transforms: [myTransform]})

redux-persist 提供了一些社区提供的 transform ,可以参考官网 Transforms:

  • immutable - support immutable reducers

  • compress - compress your serialized state with lz-string

  • encrypt - encrypt your serialized state with AES

  • filter - store or load a subset of your state

回顾

今天分享了怎么使用 redux-persist 用面向切面的方式优雅的解决 store 持久化。redux 优雅的设计给这种可能带来土壤。 另外自己读了挺多 react-native 的源码,也看了很多网友分析的文章,总是觉得不够简单易读,更像写给自己的备忘录,很想提笔写又诚惶诚恐,因为 react-native 涉及的语言和知识点都比较多,希望想看的朋友可以给些建议,:)。