web-dev-qa-db-ja.com

更新時にredux状態ツリーを保持するにはどうすればよいですか?

Reduxドキュメントの最初のプリンシパルは次のとおりです。

アプリケーション全体の状態は、単一のストア内のオブジェクトツリーに保存されます。

そして、私は実際にすべての校長をよく理解していると思いました。しかし、私は今、アプリケーションが何を意味するのか混乱しています。

アプリケーションがウェブサイトの小さな複雑な部分の1つを意味し、1ページだけで機能する場合、私は理解しています。しかし、アプリケーションがウェブサイト全体を意味する場合はどうでしょうか?状態ツリーを保持するために、LocalStorageまたはCookieなどを使用する必要がありますか?しかし、ブラウザがLocalStorageをサポートしていない場合はどうなりますか?

開発者が状態ツリーをどのように保持しているかを知りたい! :)

55
incleaf

ブラウザの更新後もredux状態を維持したい場合は、reduxミドルウェアを使用してこれを行うのが最善です。 redux-persist および redux-storage ミドルウェアを確認してください。両方とも、redux状態を保存するという同じタスクを実行して、自由に保存およびロードできるようにします。

-

編集

私がこの質問を再訪してからしばらく経ちましたが、もう一方(より賛成の答えですが)があなた自身のソリューションの展開を奨励しているのを見て、私はこれにもう一度答えると思いました。

この編集の時点で、両方のライブラリは過去6か月以内に更新されています。私のチームは数年前から本番環境でredux-persistを使用しており、問題はありませんでした。

単純な問題のように思えるかもしれませんが、独自のソリューションを導入すると、メンテナンスの負担が生じるだけでなく、バ​​グやパフォーマンスの問題が発生することがすぐにわかります。最初に思い浮かぶ例は次のとおりです。

  1. JSON.stringifyおよびJSON.parseは、不要な場合にパフォーマンスを低下させるだけでなく、reduxストアなどの重要なコードで処理しないとアプリケーションをクラッシュさせる可能性があるエラーをスローする可能性があります。
  2. (以下の回答で部分的に言及されています):アプリの状態をいつどのように保存および復元するかを理解することは簡単な問題ではありません。頻繁に行うと、パフォーマンスが低下します。十分ではない、または状態の間違った部分が保持されている場合は、より多くのバグが発生する可能性があります。上記のライブラリは、そのアプローチでバトルテストされており、動作をカスタマイズするかなり簡単な方法を提供します。
  3. Redux(特にReactエコシステム)の美しさの一部は、複数の環境に配置できることです。この編集の時点で、redux-persistには 15種類のストレージ実装 があります。これには、素晴らしい localForageライブラリ が含まれています。また、React Nativeのサポート、電子およびノー​​ド。

まとめると、 2.5kB縮小+ gzip圧縮済み これは問題ではないので、自分のチームに自分で解決するよう依頼します。

54
michaelgmcd

編集2019年8月25日

コメントの1つで述べたように。元の redux-storage パッケージは react-stack に移動しました。このアプローチは、独自の状態管理ソリューションの実装に引き続き焦点を当てています。


元の回答

提供された答えはある時点で有効でしたが、元の redux-storage パッケージが廃止され、メンテナンスされていないことに注意することが重要です...

パッケージredux-storageの元の作成者は、プロジェクトを廃止することを決定し、メンテナンスされなくなりました。

将来、これらのような問題を回避するために他のパッケージに依存したくない場合は、独自のソリューションを簡単に展開できます。

あなたがする必要があるのは:

1- localStorageから状態を返す関数を作成し、ストアをハイドレイトするために、2番目のパラメーターで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に保存する関数をデバウンスまたはスロットルすることができます。

70
Leo

これは、Leoの回答に基づいています(サードパーティのライブラリを使用せずに質問の目的を達成するため、これは受け入れられた回答である必要があります)。

Reduxストアを作成し、ローカルストレージを使用して永続化し、ゲッターを介してストアに簡単にアクセスできるのシングルトンクラスを作成しました。

これを使用するには、次の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;
0
thgc