【问题标题】:How can I persist redux state tree on refresh?如何在刷新时保留 redux 状态树?
【发布时间】:2016-09-08 19:11:57
【问题描述】:

Redux 文档的第一条原则是:

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

而且我实际上认为我很好地理解了所有原则。 但是我现在很困惑,应用程序是什么意思。

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

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

【问题讨论】:

  • 这是一个广泛的问题。你可以做任何你提到的事情。您是否有想要分享的代码来向我们展示您尝试过什么但没有奏效?您可以将整个网站实现为一个实体,也可以拥有多个。你可以使用 localStorage 来持久化数据,或者一个真实的数据库,或者两者都不是。应用意味着活的、活跃的实例。在大多数情况下,这只是一个,你的根。但是,同样,有很多方法可以实现应用程序。

标签: reactjs redux flux


【解决方案1】:

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

--

编辑

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

截至本次编辑,这两个库均在过去六个月内进行了更新。我的团队已经在生产环境中使用 redux-persist 几年了,没有遇到任何问题。

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

  1. JSON.stringifyJSON.parse 不仅会在不需要时损害性能,而且会抛出错误,如果在您的 redux 商店等关键代码中未处理这些错误可能会使您的应用程序崩溃。
  2. (在下面的答案中部分提到):弄清楚何时以及如何保存和恢复您的应用程序状态并不是一个简单的问题。这样做太频繁,你会损害性能。还不够,或者如果状态的错误部分持续存在,您可能会发现自己有更多的错误。上面提到的库在其方法中经过了实战考验,并提供了一些非常简单的方法来自定义其行为。
  3. redux 的部分优点(尤其是在 React 生态系统中)在于它能够放置在多个环境中。在本次编辑中,redux-persist 拥有 15 different storage implementations,包括出色的 web 版 localForage library,以及对 React Native、Electron 和 Node 的支持。

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

【讨论】:

  • 我可以推荐 redux-persist(还没有尝试过 redux-storage),但它对我来说非常好用,只需很少的配置和设置。
  • 截至目前,这两个库都已死去,并且在 2 年前的最后一次提交中没有维护。
  • 看起来 redux-persist 又回来了一点,在我写这篇文章的 22 天前发布了一个新版本
  • redux-storage 的新位置是github.com/react-stack/redux-storage
  • 关于这个答案的注意事项:现实情况是,软件和库通常采用基于社区(支持)的方法,甚至是编程语言的一些非常重要的模块由第三方/库支持。 通常,开发人员必须密切关注其堆栈中使用的每个工具,以了解它是否被弃用/更新。两种选择; 1. 实施您自己的并不断开发以确保性能和跨平台标准。 2. 使用 久经考验 的解决方案,并且只检查更新/建议,就像 @MiFreidgeimSO-stopbeingevil 所说的那样
【解决方案2】:

2019 年 8 月 25 日编辑

如其中一个 cmets 所述。原来的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());
});

希望对某人有所帮助

性能说明

如果您的应用程序中的状态更改非常频繁,那么过于频繁地保存到本地存储可能会损害您的应用程序的性能,尤其是在要序列化/反序列化的状态对象图很大的情况下。对于这些情况,您可能需要使用 RxJslodash 或类似的东西来去抖动或限制将状态保存到 localStorage 的函数。

【讨论】:

  • 我更喜欢这种方法,而不是使用中间件。感谢您提供有关性能问题的提示。
  • 绝对是首选答案。但是,当我刷新页面并在创建商店时从 localstorage 加载状态时,我收到几个警告,其中包括文本“在减速器收到的先前状态中找到的意外属性 [容器名称]。预计会找到其中一个已知的减速器属性名称:“全局”,“语言”。意外的属性将被忽略。它仍然有效,并且基本上抱怨在创建商店时它不知道所有其他容器。有没有绕过这个警告?
  • @Zief 很难说。消息“似乎”很清楚,reducers 期望未指定的属性。可能与为序列化状态提供默认值有关?
  • 非常简单的解决方案。谢谢。
  • @Joezhou 很想听听您为什么喜欢这种方法。就个人而言,这似乎正是中间件的用途。
【解决方案3】:

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

我创建了一个 Singleton 类,它创建一个 Redux Store,使用本地存储持久化它,并允许通过 getter 简单地访问它的 store

要使用它,只需将以下 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;

【讨论】:

    猜你喜欢
    • 2018-07-25
    • 2020-06-09
    • 1970-01-01
    • 2013-09-28
    • 2020-09-07
    • 2021-11-27
    • 2018-04-25
    • 2018-05-29
    • 1970-01-01
    相关资源
    最近更新 更多