Voglio persistere alcune parti del mio albero di stato nel localStorage. Qual è il posto adatto per farlo? Riduttore o azione?
Voglio persistere alcune parti del mio albero di stato nel localStorage. Qual è il posto adatto per farlo? Riduttore o azione?
Risposte:
Il riduttore non è mai il luogo adatto per farlo perché i riduttori dovrebbero essere puri e senza effetti collaterali.
Consiglierei di farlo in un abbonato:
store.subscribe(() => {
// persist your state
})
Prima di creare il negozio, leggi quelle parti persistenti:
const persistedState = // ...
const store = createStore(reducer, persistedState)
Se si utilizza, combineReducers()
si noterà che i riduttori che non hanno ricevuto lo stato "si avvieranno" normalmente utilizzando il valore state
dell'argomento predefinito . Questo può essere molto utile.
Si consiglia di rimbalzare l'abbonato in modo da non scrivere troppo velocemente su localStorage, altrimenti si avranno problemi di prestazioni.
Infine, puoi creare un middleware che lo incapsula come alternativa, ma inizierei con un abbonato perché è una soluzione più semplice e fa bene il lavoro.
Per riempire gli spazi vuoti della risposta di Dan Abramov è possibile utilizzare in store.subscribe()
questo modo:
store.subscribe(()=>{
localStorage.setItem('reduxState', JSON.stringify(store.getState()))
})
Prima di creare il negozio, controlla localStorage
e analizza qualsiasi JSON sotto la tua chiave in questo modo:
const persistedState = localStorage.getItem('reduxState')
? JSON.parse(localStorage.getItem('reduxState'))
: {}
Quindi passi questa persistedState
costante al tuo createStore
metodo in questo modo:
const store = createStore(
reducer,
persistedState,
/* any middleware... */
)
persistedState
restituito initialState
anziché un oggetto vuoto? Altrimenti penso che createStore
verrà inizializzato con quell'oggetto vuoto.
In una parola: middleware.
Dai un'occhiata a redux-persist . O scrivi il tuo.
[AGGIORNAMENTO 18 dic 2016] Modificato per rimuovere la menzione di due progetti simili ora inattivi o obsoleti.
Se qualcuno ha qualche problema con le soluzioni di cui sopra, puoi scriverne uno tuo. Lascia che ti mostri cosa ho fatto. Ignora le saga middleware
cose, concentrati solo su due cose localStorageMiddleware
e reHydrateStore
metodi. il localStorageMiddleware
tirare tutte le redux state
e lo mette in local storage
e rehydrateStore
tirare tutti i applicationState
nella memoria locale se presente e lo mette inredux 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;
localStorage
anche se nulla nel negozio è cambiato? Come compensare le scritture non necessarie
Non posso rispondere a @Gardezi ma un'opzione basata sul suo codice potrebbe essere:
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
)
);
la differenza è che stiamo solo salvando alcune azioni, potresti usare una funzione di debounce per salvare solo l'ultima interazione del tuo stato
Sono un po 'in ritardo ma ho implementato uno stato persistente secondo gli esempi qui riportati. Se vuoi aggiornare lo stato solo ogni X secondi, questo approccio può aiutarti:
Definire una funzione wrapper
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!")
}
}
Chiama una funzione wrapper nel tuo abbonato
store.subscribe((state) =>
{
updateLocalStorage(store.getState())
});
In questo esempio, lo stato viene aggiornato al massimo ogni 5 secondi, indipendentemente dalla frequenza con cui viene attivato un aggiornamento.
(state) => { updateLocalStorage(store.getState()) }
in lodash.throttle
questo modo: store.subscribe(throttle(() => {(state) => { updateLocalStorage(store.getState())} }
e togliere tempo a controllare la logica interna.
Sulla base degli eccellenti suggerimenti e degli estratti di codici brevi forniti in altre risposte (e nell'articolo Medium di Jam Creencia ), ecco una soluzione completa!
Abbiamo bisogno di un file contenente 2 funzioni che salvano / caricano lo stato nella / dalla memoria locale:
// 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;
}
};
Queste funzioni sono importate da store.js dove configuriamo il nostro negozio:
NOTA: è necessario aggiungere una dipendenza: 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;
Il negozio viene importato in index.js in modo che possa essere passato nel provider che avvolge 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')
)
Si noti che le importazioni assolute richiedono questa modifica in YourProjectFolder / jsconfig.json - questo indica dove cercare i file se non li trova all'inizio. Altrimenti, vedrai lamentele riguardo al tentativo di importare qualcosa dall'esterno di src .
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}