Redux ऐप में लोकलस्टोरेज को कहां लिखें?


166

मैं अपने स्टेट ट्री के कुछ हिस्सों को लोकलस्टोरेज पर कायम रखना चाहता हूं। ऐसा करने के लिए उपयुक्त स्थान क्या है? Reducer या कार्रवाई?

जवाबों:


223

Reducer ऐसा करने के लिए एक उपयुक्त स्थान नहीं है क्योंकि Reducers शुद्ध होने चाहिए और कोई साइड इफेक्ट नहीं होना चाहिए।

मैं इसे एक ग्राहक में करने की सलाह दूंगा:

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

स्टोर बनाने से पहले, उन भागों को पढ़ें:

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

यदि आप उपयोग करते हैं, combineReducers()तो आप देखेंगे कि राज्य को प्राप्त नहीं हुए रिड्यूसर अपने डिफ़ॉल्ट stateतर्क मान का उपयोग करके सामान्य रूप से "बूट" करेंगे । यह बहुत आसान हो सकता है।

यह सलाह दी जाती है कि आप अपने सब्सक्राइबर की आलोचना करें ताकि आप लोकलस्टोरेज को बहुत तेजी से न लिखें, या आपको प्रदर्शन की समस्या हो।

अंत में, आप एक मिडलवेयर बना सकते हैं जो एक विकल्प के रूप में एनकैप्सुलेट करता है, लेकिन मैं एक सब्सक्राइबर के साथ शुरू करूंगा क्योंकि यह एक सरल समाधान है और यह अच्छी तरह से काम करता है।


1
क्या होगा अगर मैं राज्य के सिर्फ एक हिस्से की सदस्यता लेना चाहूंगा? क्या यह संभव है? मैं कुछ अलग सा कुछ लेकर आगे बढ़ा। 2. लोड रिड्यूसर (या घटकविलीमाउंट के साथ) के अंदर रिड्यूस्ड स्टेट को रिडक्स एक्शन के साथ लोड करें सीधे स्टोर पर मौजूद डेटा को लोड करने के बजाय 2 बिलकुल ठीक है? (जो मैं SSR के लिए अलग रखने की कोशिश कर रहा हूं)। वैसे Redux के लिए धन्यवाद! यह बहुत अच्छा है, मैं इसे प्यार करता हूँ, मेरे बड़े प्रोजेक्ट कोड के साथ खो जाने के बाद अब यह सब वापस सरल और अनुमानित है :)
मार्क Uretsky

store.subscribe callback के भीतर, आपके पास वर्तमान स्टोर डेटा तक पूरी पहुंच है, इसलिए आप अपनी रुचि के किसी भी हिस्से को जारी रख सकते हैं
Bo Chen

35
Dan Abramov ने इस पर एक पूरा वीडियो भी बनाया है: egghead.io/lessons/…
NateW

2
क्या प्रत्येक स्टोर अपडेट पर राज्य को क्रमबद्ध करने के साथ वैध प्रदर्शन चिंताएं हैं? क्या ब्राउज़र अलग थ्रेड पर अनुक्रमित हो सकता है?
स्टीफन पॉल

7
यह संभावित रूप से बेकार लग रहा है। ओपी ने कहा कि केवल राज्य के एक हिस्से को बनाए रखने की आवश्यकता है। मान लीजिए कि आपके पास 100 विभिन्न कुंजियों वाला एक स्टोर है। आप केवल उनमें से 3 को लगातार बनाए रखना चाहते हैं जो बार-बार बदले जाते हैं। अब आप अपने 100 कुंजी में से किसी भी छोटे परिवर्तन पर स्थानीय स्टोर को पार्स कर रहे हैं और अपडेट कर रहे हैं, जबकि आपके द्वारा बनाए रखने में रुचि रखने वाले 3 कुंजी में से कोई भी कभी भी बदला नहीं गया है। नीचे दिए गए @Gardezi द्वारा समाधान एक बेहतर तरीका है क्योंकि आप अपने श्रोताओं में ईवेंट श्रोताओं को जोड़ सकते हैं ताकि आप केवल तभी अपडेट करें जब आपको वास्तव में उदा चाहिए: codepen.io/retrcult/pen/qNRzKN
Anna T

153

दान Abramov के जवाब के रिक्त स्थान को भरने के लिए आप 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
अतिरिक्त निर्भरता के बिना सरल और प्रभावी।
एक्सपेक्टेक्ट

1
एक मिडलवेयर बनाना थोड़ा बेहतर लग सकता है, लेकिन मैं मानता हूं कि यह प्रभावी है और ज्यादातर मामलों के लिए पर्याप्त है
Bo Chen

क्या यह करने का एक अच्छा तरीका है जब CombReducers का उपयोग किया जाए, और आप केवल एक स्टोर को बनाए रखना चाहते हैं?
ब्रेंडैंगिबसन

1
यदि लोकलस्टोरेज में कुछ भी नहीं है, तो खाली वस्तु के बजाय persistedStateवापस लौटना चाहिए initialState? अन्यथा मुझे लगता है createStoreकि उस खाली वस्तु के साथ आरंभ होगा।
एलेक्स

1
@ एलेक्स आप सही हैं, जब तक कि आपका इनिशियलस्टेट खाली न हो।
लिंक 14

47

एक शब्द में: मिडलवेयर।

Redux-persist की जाँच करें । या अपना खुद का लिखो।

[अद्यतन १ UP दिसंबर २०१६] दो समान परियोजनाओं के उल्लेख को हटाने के लिए संपादित अब निष्क्रिय या पदावनत।


12

यदि किसी को उपरोक्त समाधानों से कोई समस्या हो रही है, तो आप अपना स्वयं का लिख ​​सकते हैं। मैं तुम्हें दिखाता हूं कि मैंने क्या किया। saga middlewareचीजों को नजरअंदाज करें बस दो चीजों localStorageMiddlewareऔर reHydrateStoreविधि पर ध्यान दें । localStorageMiddlewareपुल सब redux stateऔर में डालता है यह local storageऔर rehydrateStoreखींच सभी 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

खैर, यह काम करता है, वैसे भी यह अच्छा विचार है? प्रश्न: बड़े डेटा वाले अधिक डेटा मेन्यू कई रिड्यूसर होने की स्थिति में हमें क्या खुशी होगी?
कुंवर सिंह

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.throttleइस तरह: store.subscribe(throttle(() => {(state) => { updateLocalStorage(store.getState())} }और तर्क के अंदर की जाँच के समय को हटा दें।
जॉर्जमा

1

अन्य सुझावों (और जाम क्रेन्सिया के मध्यम लेख ) में प्रदान किए गए उत्कृष्ट सुझावों और लघु कोड अंशों का निर्माण , यहाँ एक पूर्ण समाधान है!

हमें एक फ़ाइल की आवश्यकता है जिसमें 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 द्वारा आयात किया जाता है जहां हम अपने स्टोर को कॉन्फ़िगर करते हैं:

नोट: आपको एक निर्भरता जोड़ने की आवश्यकता होगी: 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"]
}

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.