Chcę zachować niektóre części mojego drzewa stanu w magazynie localStorage. Jakie jest odpowiednie miejsce, aby to zrobić? Reduktor czy akcja?
Chcę zachować niektóre części mojego drzewa stanu w magazynie localStorage. Jakie jest odpowiednie miejsce, aby to zrobić? Reduktor czy akcja?
Odpowiedzi:
Reduktor nigdy nie jest odpowiednim miejscem do tego, ponieważ reduktory powinny być czyste i nie mieć skutków ubocznych.
Poleciłbym po prostu zrobić to w subskrybencie:
store.subscribe(() => {
// persist your state
})
Przed utworzeniem sklepu przeczytaj te utrwalone części:
const persistedState = // ...
const store = createStore(reducer, persistedState)
Jeśli użyjesz combineReducers(), zauważysz, że reduktory, które nie otrzymały stanu, „uruchomią się” normalnie, używając domyślnej statewartości argumentu. To może być bardzo przydatne.
Wskazane jest, aby usunąć subskrybenta, aby nie pisać zbyt szybko do localStorage, w przeciwnym razie będziesz mieć problemy z wydajnością.
Na koniec możesz stworzyć oprogramowanie pośredniczące, które hermetyzuje to jako alternatywę, ale zacznę od subskrybenta, ponieważ jest to prostsze rozwiązanie i dobrze spełnia swoje zadanie.
Aby wypełnić puste miejsca w odpowiedzi Dana Abramowa, możesz użyć store.subscribe()tego:
store.subscribe(()=>{
localStorage.setItem('reduxState', JSON.stringify(store.getState()))
})
Przed utworzeniem sklepu sprawdź localStoragei przeanalizuj dowolny kod JSON pod kluczem w następujący sposób:
const persistedState = localStorage.getItem('reduxState')
? JSON.parse(localStorage.getItem('reduxState'))
: {}
Następnie przekazujesz tę persistedStatestałą do swojej createStoremetody w następujący sposób:
const store = createStore(
reducer,
persistedState,
/* any middleware... */
)
persistedStatezwrócić initialStatezamiast pustego obiektu? W przeciwnym razie myślę, że createStorezainicjuje się z tym pustym obiektem.
Jednym słowem: oprogramowanie pośredniczące.
Sprawdź redux-persist . Lub napisz własne.
[AKTUALIZACJA 18 grudnia 2016 r.] Zmieniono, aby usunąć wzmiankę o dwóch podobnych projektach, które są obecnie nieaktywne lub wycofane.
Jeśli ktoś ma jakiś problem z powyższymi rozwiązaniami, możesz napisać własne na adres. Pokażę ci, co zrobiłem. Ignoruj saga middlewarerzeczy, skup się tylko na dwóch rzeczach localStorageMiddlewarei reHydrateStoremetodzie. localStorageMiddlewarepociągają za wszystkie redux statei umieszcza go w local storagei rehydrateStorewyciągnąć wszystko applicationStatew pamięci lokalnej, jeśli obecne i umieszcza go wredux 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;
localStoragenawet jeśli nic w sklepie się nie zmieniło? Jak rekompensujesz niepotrzebne zapisy
Nie mogę odpowiedzieć na @Gardezi, ale opcją opartą na jego kodzie może być:
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
)
);
różnica polega na tym, że po prostu zapisujemy niektóre akcje, możesz użyć funkcji odbicia, aby zapisać tylko ostatnią interakcję twojego stanu
Trochę się spóźniłem, ale zaimplementowałem stan trwały zgodnie z podanymi tutaj przykładami. Jeśli chcesz aktualizować stan tylko co X sekund, to podejście może Ci pomóc:
Zdefiniuj funkcję opakowania
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!")
}
}Wywołaj funkcję opakowania w swoim subskrybencie
store.subscribe((state) =>
{
updateLocalStorage(store.getState())
});W tym przykładzie stan jest aktualizowany co najwyżej co 5 sekund, niezależnie od częstotliwości wyzwalania aktualizacji.
(state) => { updateLocalStorage(store.getState()) }w lodash.throttleten sposób: store.subscribe(throttle(() => {(state) => { updateLocalStorage(store.getState())} }i usunąć z niego logikę sprawdzania czasu.
Opierając się na doskonałych sugestiach i krótkich fragmentach kodu zawartych w innych odpowiedziach (i artykule Jam Creencia w Medium ), oto kompletne rozwiązanie!
Potrzebujemy pliku zawierającego 2 funkcje, które zapisują / ładują stan do / z lokalnej pamięci:
// 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;
}
};
Te funkcje są importowane przez store.js, gdzie konfigurujemy nasz sklep:
UWAGA: Musisz dodać jedną zależność: 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;
Sklep jest importowany do pliku index.js, dzięki czemu można go przekazać do dostawcy opakowującego plik 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')
)
Zwróć uwagę, że import bezwzględny wymaga tej zmiany w YourProjectFolder / jsconfig.json - to mówi mu, gdzie szukać plików, jeśli nie może ich na początku znaleźć. W przeciwnym razie zobaczysz skargi dotyczące próby zaimportowania czegoś spoza src .
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}