ReduxアプリでlocalStorageに書き込む場所は?


回答:


223

レデューサーは純粋で副作用がないはずなので、レデューサーはこれを行うのに適切な場所ではありません。

サブスクライバーで実行することをお勧めします。

store.subscribe(() => {
  // persist your state
})

ストアを作成する前に、これらの永続化された部分を読んでください。

const persistedState = // ...
const store = createStore(reducer, persistedState)

を使用するcombineReducers()と、状態を受け取っていないレデューサーは、デフォルトのstate引数値を使用して通常どおり「起動」します。これはかなり便利です。

localStorageへの書き込みが速すぎないようにサブスクライバーをデバウンスすることをお勧めします。そうしないと、パフォーマンスの問題が発生します。

最後に、それを代替としてカプセル化するミドルウェアを作成できますが、私はサブスクライバーから始めます。シンプルなソリューションであり、うまく機能するからです。


1
州の一部のみを購読したい場合はどうなりますか?それは可能ですか?私は少し違うものを先に進めました:1. reduxの外で永続化状態を保存します。2. reduxアクションを使用して、reactコンストラクター内に(またはcomponentWillMountを使用して)永続化状態をロードします。ストアに永続化されたデータを直接ロードする代わりに、2は完全に問題ありませんか?(私はSSRのために別に保持しようとしています)。ちなみにReduxに感謝!それは
素晴らしく

store.subscribeコールバック内では、現在のストアデータに完全にアクセスできるため、興味のある部分を永続化できます
Bo Chen

35
ダンアブラモフ氏は、ビデオ全体も作成しています。egghead.io
lessons

2
ストアを更新するたびに状態をシリアル化することには、正当なパフォーマンスの問題がありますか?ブラウザは別のスレッドでシリアル化しますか?
スティーブンポール

7
これは潜在的に無駄に見えます。OPは、状態の一部のみを永続化する必要があると述べました。100の異なるキーを持つストアがあるとします。まれにしか変更されない3つだけを永続化したい。これで、100個のキーのいずれかを少し変更するたびにローカルストアを解析および更新していますが、永続化する3つのキーはどれも変更されていません。以下の@Gardeziによる解決策は、イベントリスナーをミドルウェアに追加して、実際に必要なときにのみ更新できるようにするための優れたアプローチです。例:codepen.io/retrcult/pen/qNRzKN
Anna T

153

ダンアブラモフの答えの空白を埋めるには、次のstore.subscribe()ように使用できます。

store.subscribe(()=>{
  localStorage.setItem('reduxState', JSON.stringify(store.getState()))
})

ストアを作成する前に、次のlocalStorageようにキーの下のJSONを確認して解析します。

const persistedState = localStorage.getItem('reduxState') 
                       ? JSON.parse(localStorage.getItem('reduxState'))
                       : {}

次に、このpersistedState定数を次のようにcreateStoreメソッドに渡します。

const store = createStore(
  reducer, 
  persistedState,
  /* any middleware... */
)

6
シンプルで効果的で、追加の依存関係はありません。
AxeEffect 2016年

1
ミドルウェアを作成すると少し見栄えがよくなるかもしれませんが、それが効果的でほとんどの場合十分であることに同意します
Bo Chen

CombineReducersを使用しているときにこれを行うための良い方法はありますか。
brendangibson 2017年

1
localStorageに何もない場合、空のオブジェクトの代わりにpersistedState返す必要initialStateがありますか?そうでなければcreateStore、その空のオブジェクトで初期化すると思います。
アレックス

1
@Alex初期状態が空でない限り、そのとおりです。
Link14

47

つまり、ミドルウェアです。

redux-persistをチェックしてください。または独自に記述します。

[2016年12月18日更新]現在非アクティブまたは非推奨となっている2つの類似プロジェクトの言及を削除するように編集されました。


12

上記の解決策で問題が発生した場合は、独自に作成できます。私がやったことをお見せしましょう。無視するsaga middlewareことは、2つのことlocalStorageMiddlewarereHydrateStore方法にのみ焦点を当てています。localStorageMiddlewareプルすべてredux stateとプットそれでlocal storagerehydrateStoreすべての引きapplicationState存在とを入れ、それを場合、ローカルストレージにredux store

import {createStore, applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga';
import decoristReducers from '../reducers/decorist_reducer'

import sagas from '../sagas/sagas';

const sagaMiddleware = createSagaMiddleware();

/**
 * Add all the state in local storage
 * @param getState
 * @returns {function(*): function(*=)}
 */
const localStorageMiddleware = ({getState}) => { // <--- FOCUS HERE
    return (next) => (action) => {
        const result = next(action);
        localStorage.setItem('applicationState', JSON.stringify(
            getState()
        ));
        return result;
    };
};


const reHydrateStore = () => { // <-- FOCUS HERE

    if (localStorage.getItem('applicationState') !== null) {
        return JSON.parse(localStorage.getItem('applicationState')) // re-hydrate the store

    }
}


const store = createStore(
    decoristReducers,
    reHydrateStore(),// <-- FOCUS HERE
    applyMiddleware(
        sagaMiddleware,
        localStorageMiddleware,// <-- FOCUS HERE 
    )
)

sagaMiddleware.run(sagas);

export default store;

2
こんにちは、これによりlocalStorage、ストア内で何も変更されていない場合でも、大量の書き込みが発生しませんか?あなたは、不要な書き込みを補う方法
user566245

まあ、それはうまくいきます、とにかくそれはこの良い考えですか?質問:大量のデータを持つ複数のレデューサーでデータメナが増えるとどうなりますか?
Kunvar Singh

3

@Gardeziには答えられませんが、彼のコードに基づくオプションは次のようになります。

const rootReducer = combineReducers({
    users: authReducer,
});

const localStorageMiddleware = ({ getState }) => {
    return next => action => {
        const result = next(action);
        if ([ ACTIONS.LOGIN ].includes(result.type)) {
            localStorage.setItem(appConstants.APP_STATE, JSON.stringify(getState()))
        }
        return result;
    };
};

const reHydrateStore = () => {
    const data = localStorage.getItem(appConstants.APP_STATE);
    if (data) {
        return JSON.parse(data);
    }
    return undefined;
};

return createStore(
    rootReducer,
    reHydrateStore(),
    applyMiddleware(
        thunk,
        localStorageMiddleware
    )
);

違いは、いくつかのアクションを保存するだけです。イベントのデバウンス機能を使用して、状態の最後の相互作用のみを保存できます。


1

私は少し遅れましたが、ここに記載されている例に従って永続的な状態を実装しました。X秒ごとにのみ状態を更新する場合は、このアプローチが役立ちます。

  1. ラッパー関数を定義する

    let oldTimeStamp = (Date.now()).valueOf()
    const millisecondsBetween = 5000 // Each X milliseconds
    function updateLocalStorage(newState)
    {
        if(((Date.now()).valueOf() - oldTimeStamp) > millisecondsBetween)
        {
            saveStateToLocalStorage(newState)
            oldTimeStamp = (Date.now()).valueOf()
            console.log("Updated!")
        }
    }
  2. サブスクライバーでラッパー関数を呼び出す

        store.subscribe((state) =>
        {
        updateLocalStorage(store.getState())
         });

この例では、更新がトリガーされる頻度に関係なく、状態は最大で 5秒ごとに更新さます。


1
あなたはこのようにラップ(state) => { updateLocalStorage(store.getState()) }することができますlodash.throttlestore.subscribe(throttle(() => {(state) => { updateLocalStorage(store.getState())} }そして内部の時間チェックロジックを削除します。
GeorgeMA

1

他の回答で提供された優れた提案と短いコードの抜粋(およびJam CreenciaのMediumの記事))に、完全なソリューションをします。

ローカルストレージとの間で状態を保存/ロードする2つの関数を含むファイルが必要です。

// FILE: src/common/localStorage/localStorage.js

// Pass in Redux store's state to save it to the user's browser local storage
export const saveState = (state) =>
{
  try
  {
    const serializedState = JSON.stringify(state);
    localStorage.setItem('state', serializedState);
  }
  catch
  {
    // We'll just ignore write errors
  }
};



// Loads the state and returns an object that can be provided as the
// preloadedState parameter of store.js's call to configureStore
export const loadState = () =>
{
  try
  {
    const serializedState = localStorage.getItem('state');
    if (serializedState === null)
    {
      return undefined;
    }
    return JSON.parse(serializedState);
  }
  catch (error)
  {
    return undefined;
  }
};

これらの関数は、ストアを構成するstore.jsによってインポートされます。

注:1つの依存関係を追加する必要があります。 npm install lodash.throttle

// FILE: src/app/redux/store.js

import { configureStore, applyMiddleware } from '@reduxjs/toolkit'

import throttle from 'lodash.throttle';

import rootReducer from "./rootReducer";
import middleware from './middleware';

import { saveState, loadState } from 'common/localStorage/localStorage';


// By providing a preloaded state (loaded from local storage), we can persist
// the state across the user's visits to the web app.
//
// READ: https://redux.js.org/recipes/configuring-your-store
const store = configureStore({
	reducer: rootReducer,
	middleware: middleware,
	enhancer: applyMiddleware(...middleware),
	preloadedState: loadState()
})


// We'll subscribe to state changes, saving the store's state to the browser's
// local storage. We'll throttle this to prevent excessive work.
store.subscribe(
	throttle( () => saveState(store.getState()), 1000)
);


export default store;

ストアはindex.jsにインポートされるため、App.jsをラップするプロバイダーに渡すことができます。

// FILE: src/index.js

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'

import App from './app/core/App'

import store from './app/redux/store';


// Provider makes the Redux store available to any nested components
render(
	<Provider store={store}>
		<App />
	</Provider>,
	document.getElementById('root')
)

絶対インポートでは、YourProjectFolder / jsconfig.jsonにこの変更を加える必要があることに注意してくださいこれにより、最初にファイルを見つけることができない場合に、ファイルを探す場所がわかります。それ以外の場合は、srcの外部から何かをインポートしようとすることに関する苦情が表示されます。

{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]
}

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.