如何在刷新时保留 redux 状态树?

How can I persist redux state tree on refresh?

Redux 文档的首要原则是:

The state of your whole application is stored in an object tree within a single store.

我实际上认为我理解所有的原则。 但是我现在很困惑,申请是什么意思

如果应用程序只是网站中的一个小复杂部分,并且只在一个页面中运行,我理解。但是,如果应用程序意味着整个网站呢?我应该使用 LocalStorage 或 cookie 或其他东西来保存状态树吗?但是如果浏览器不支持LocalStorage怎么办?

我想知道开发人员如何保存他们的状态树! :)

如果您想在浏览器刷新时保持 redux 状态,最好使用 redux 中间件来实现。查看 redux-persist and redux-storage 中间件。他们都试图完成存储 redux 状态的相同任务,以便可以随意保存和加载它。

--

编辑

自从我重新审视这个问题以来已经有一段时间了,但是看到另一个(尽管更受欢迎的答案)鼓励推出你自己的解决方案,我想我会再次回答这个问题。

截至此次编辑,这两个库都在过去六个月内进行了更新。我的团队多年来一直在生产中使用 redux-persist 并且没有遇到任何问题。

虽然这似乎是一个简单的问题,但您很快就会发现,采用自己的解决方案不仅会造成维护负担,还会导致错误和性能问题。想到的第一个例子是:

  1. JSON.stringifyJSON.parse 不仅会在不需要时损害性能,而且会抛出错误,如果在关键代码段(如 redux 存储)中未处理,可能会导致应用程序崩溃。
  2. (在下面的答案中部分提到):弄清楚何时以及如何保存和恢复您的应用程序状态不是一个简单的问题。经常这样做会影响性能。不够,或者如果状态的错误部分持续存在,您可能会发现自己有更多的错误。上面提到的库在他们的方法中经过了实战测试,并提供了一些非常简单的方法来定制他们的行为。
  3. redux(尤其是在 React 生态系统中)的部分优点在于它能够放置在多个环境中。截至此次编辑,redux-persist 已 15 different storage implementations, including the awesome localForage library 用于 Web,并支持 React Native、Electron 和 Node。

总而言之,for 3kB minified + gzipped(在编辑时)这不是我会要求我的团队自行解决的问题。

编辑 2019 年 8 月 25 日

如其中一条评论所述。原文redux-storage package has been moved to react-stack。这种方法仍然侧重于实现您自己的状态管理解决方案。


原答案

虽然提供的答案在某些时候是有效的,但重要的是要注意原始的 redux-storage 包已被弃用并且不再维护...

The original author of the package redux-storage has decided to deprecate the project and no longer maintained.

现在,如果您不想依赖其他包以避免将来出现此类问题,那么推出您自己的解决方案非常容易。

您只需:

1- 创建一个函数,returns 来自 localStorage 的状态,然后在第二个参数中将状态传递给 createStore 的 redux 函数,以补充存储

 const store = createStore(appReducers, state);

2-监听状态变化,每次状态变化时,将状态保存到localStorage

store.subscribe(() => {
    //this is just a function that saves state to localStorage
    saveState(store.getState());
}); 

就是这样......我实际上在生产中使用了类似的东西,但我没有使用函数,而是写了一个非常简单的 class 如下......

class StateLoader {

    loadState() {
        try {
            let serializedState = localStorage.getItem("http://contoso.com:state");

            if (serializedState === null) {
                return this.initializeState();
            }

            return JSON.parse(serializedState);
        }
        catch (err) {
            return this.initializeState();
        }
    }

    saveState(state) {
        try {
            let serializedState = JSON.stringify(state);
            localStorage.setItem("http://contoso.com:state", serializedState);

        }
        catch (err) {
        }
    }

    initializeState() {
        return {
              //state object
            }
        };
    }
}

然后在引导您的应用时...

import StateLoader from "./state.loader"

const stateLoader = new StateLoader();

let store = createStore(appReducers, stateLoader.loadState());

store.subscribe(() => {
    stateLoader.saveState(store.getState());
});

希望对大家有所帮助

性能说明

如果应用程序中的状态更改非常频繁,则过于频繁地保存到本地存储可能会损害应用程序的性能,尤其是当 serialize/deserialize 的状态对象图很大时。对于这些情况,您可能希望使用 RxJslodash 或类似的东西去抖动或限制将状态保存到 localStorage 的函数。

这是基于 Leo 的回答(这应该是公认的答案,因为它在不使用任何第 3 方库的情况下实现了问题的目的)。

我创建了一个单例 class,它 创建了一个 Redux 存储,使用本地存储持久化它并允许通过 getter[=25= 简单地访问它的存储].

要使用它,只需将以下 Redux-Provider 元素放在主 class 周围:

// ... Your other imports
import PersistedStore from "./PersistedStore";

ReactDOM.render(
  <Provider store={PersistedStore.getDefaultStore().store}>
    <MainClass />
  </Provider>,
  document.getElementById('root')
);

并将以下 class 添加到您的项目中:

import {
  createStore
} from "redux";

import rootReducer from './RootReducer'

const LOCAL_STORAGE_NAME = "localData";

class PersistedStore {

  // Singleton property
  static DefaultStore = null;

  // Accessor to the default instance of this class
  static getDefaultStore() {
    if (PersistedStore.DefaultStore === null) {
      PersistedStore.DefaultStore = new PersistedStore();
    }

    return PersistedStore.DefaultStore;
  }

  // Redux store
  _store = null;

  // When class instance is used, initialize the store
  constructor() {
    this.initStore()
  }

  // Initialization of Redux Store
  initStore() {
    this._store = createStore(rootReducer, PersistedStore.loadState());
    this._store.subscribe(() => {
      PersistedStore.saveState(this._store.getState());
    });
  }

  // Getter to access the Redux store
  get store() {
    return this._store;
  }

  // Loading persisted state from localStorage, no need to access
  // this method from the outside
  static loadState() {
    try {
      let serializedState = localStorage.getItem(LOCAL_STORAGE_NAME);

      if (serializedState === null) {
        return PersistedStore.initialState();
      }

      return JSON.parse(serializedState);
    } catch (err) {
      return PersistedStore.initialState();
    }
  }

  // Saving persisted state to localStorage every time something
  // changes in the Redux Store (This happens because of the subscribe() 
  // in the initStore-method). No need to access this method from the outside
  static saveState(state) {
    try {
      let serializedState = JSON.stringify(state);
      localStorage.setItem(LOCAL_STORAGE_NAME, serializedState);
    } catch (err) {}
  }

  // Return whatever you want your initial state to be
  static initialState() {
    return {};
  }
}

export default PersistedStore;