如何在刷新时保持Redux状态树?


92

Redux文档的第一个原理是:

整个应用程序的状态存储在单个存储中的对象树中。

我实际上以为我对所有原则都了解得很好。但是我现在很困惑应用程序的含义。

我理解,如果应用程序仅意味着网站中复杂的小部分之一并且仅用一页即可工作。但是,如果申请意味着整个网站怎么办?我应该使用LocalStorage还是Cookie或其他某种方式保留状态树?但是如果浏览器不支持LocalStorage怎么办?

我想知道开发人员如何保留其状态树!:)


2
这是一个广泛的问题。您可以做您提到的任何事情。您是否有想要分享的代码向我们展示您尝试过的内容以及没有起作用的内容?您可以将整个网站实现为单个实体,也可以拥有多个实体。您可以使用localStorage持久存储数据,或使用真实数据库,也可以都不使用。应用意味着活跃的实例。在大多数情况下,这只是您的根源之一。但是,同样,有很多方法可以实现应用程序。
ZekeDroid '16

Answers:


88

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

-

编辑

自从我重新讨论了这个问题已经过去了一段时间,但是看到另一个问题(尽管答案更高)鼓励我提出自己的解决方案,我想我会再回答一次。

在进行此编辑时,两个库都在最近六个月内进行了更新。我的团队已经在生产中使用redux-persist几年了,没有任何问题。

尽管这似乎是一个简单的问题,但您会很快发现,推出自己的解决方案不仅会导致维护负担,还会导致错误和性能问题。我想到的第一个例子是:

  1. JSON.stringify并且JSON.parse不仅可以伤害表现在不需要的时候,但抛出的错误,当代码喜欢你的终极版商店的关键部分未处理可能会崩溃您的应用程序。
  2. (下面的答案中有部分提到):弄清楚何时以及如何保存和还原应用程序状态并不是一个简单的问题。经常这样做会损害性能。这还不够,或者如果错误的状态部分仍然存在,您可能会发现自己有更多的错误。上面提到的库在其方法上经过了实战测试,并提供了一些非常简单的方法来自定义其行为。
  3. Redux的优点(尤其是在React生态系统中)的部分原因在于它可以放置在多个环境中。截至此编辑,redux-persist具有15种不同的存储实现,包括用于Web的出色的localForage库,以及对React Native,Electron和Node的支持。

综上所述,对于缩小的3kB +压缩(在此编辑时),这不是问题,我会要求我的团队自行解决。


5
我可以推荐redux-persist(尚未尝试过redux-storage),但对我来说非常有用,只需很少的配置和设置。
larrydahooster's

截至该日期,两个库都已死了,并且直到2年前仍未维护最后一次提交。
AnBisw

1
看起来redux-persist回来了,在我撰写本文时22天前有一篇新
出版物


2
关于此答案的注释:现实情况是,软件和库通常采用基于社区(支持)的方法,即使编程语言的某些非常重要的模块也受到第三方/库的支持。通常,开发人员必须密切关注其堆栈中使用的每个工具,以了解其是否已被弃用/更新。两种选择;1.实施您自己的并不断发展,以确保性能和跨平台标准。2.使用经过战斗力测试的解决方案,并仅按@ MiFreidgeimSO-stopbeingevil的指示检查更新/建议
Geek Guy

80

编辑25-八月-2019

如评论之一所述。原始的redux-storage软件包已移至react-stack。这种方法仍然专注于实现自己的状态管理解决方案。


原始答案

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

软件包redux-storage的原始作者决定弃用该项目,不再维护。

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

您需要做的是:

1-创建一个函数,从中返回状态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 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());
});

希望对别人有帮助

性能说明

如果状态更改在应用程序中非常频繁,则过多地保存到本地存储可能会损害应用程序的性能,尤其是在要序列化/反序列化的状态对象图很大的情况下。对于这些情况,您可能希望去抖动或油门,使用保存状态的localStorage功能RxJslodash或类似的东西。


11
与使用中间件相比,我更喜欢这种方法。感谢您提供的有关性能方面的提示。
Joe Zhou

1
绝对是首选答案。但是,当我刷新页面并在创建存储时从本地存储加载状态时,我收到一些警告,其中包括“在化简器收到的先前状态中发现的文本“意外的属性[容器名称]”。已知的reducer属性名称改为:“ global”,“ language”。意外的属性将被忽略。它仍然有效,并且基本上抱怨在创建存储时它并不了解所有其他容器。该警告的方法是什么?
Zief

@Zief很难说。消息“似乎”非常清楚,减速器期望未指定的属性。可能与为序列化状态提供默认值有关?
狮子座

非常简单的解决方案。谢谢。
Ishara Madawa

1
@Joezhou很想听听您为什么喜欢这种方法。就个人而言,这似乎是中间件的确切目标。
michaelgmcd

2

这基于Leo的答案(应该是公认的答案,因为它无需使用任何第三方库即可实现问题的目的)。

我创建了一个Singleton类,该类创建一个Redux Store,使用本地存储对其进行持久化,并允许通过getter对其存储进行简单访问

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

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

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

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

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;

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.