Quiero persistir algunas partes de mi árbol de estado en localStorage. ¿Cuál es el lugar apropiado para hacerlo? ¿Reductor o acción?
Quiero persistir algunas partes de mi árbol de estado en localStorage. ¿Cuál es el lugar apropiado para hacerlo? ¿Reductor o acción?
Respuestas:
Reductor nunca es un lugar apropiado para hacer esto porque los reductores deben ser puros y no tener efectos secundarios.
Recomendaría solo hacerlo en un suscriptor:
store.subscribe(() => {
// persist your state
})
Antes de crear la tienda, lea esas partes persistentes:
const persistedState = // ...
const store = createStore(reducer, persistedState)
Si usa combineReducers()
, notará que los reductores que no han recibido el estado se "arrancarán" normalmente usando su valor de state
argumento predeterminado . Esto puede ser bastante útil.
Es recomendable que renuncies a tu suscriptor para que no escribas en localStorage demasiado rápido, o tendrás problemas de rendimiento.
Finalmente, puede crear un middleware que encapsule eso como una alternativa, pero comenzaría con un suscriptor porque es una solución más simple y funciona bien.
Para completar los espacios en blanco de la respuesta de Dan Abramov, podría usar store.subscribe()
así:
store.subscribe(()=>{
localStorage.setItem('reduxState', JSON.stringify(store.getState()))
})
Antes de crear la tienda, verifique localStorage
y analice cualquier JSON bajo su clave de esta manera:
const persistedState = localStorage.getItem('reduxState')
? JSON.parse(localStorage.getItem('reduxState'))
: {}
Luego pasa esta persistedState
constante a su createStore
método de esta manera:
const store = createStore(
reducer,
persistedState,
/* any middleware... */
)
persistedState
regresar en initialState
lugar de un objeto vacío? De lo contrario, creo que createStore
se inicializará con ese objeto vacío.
En una palabra: middleware.
Echa un vistazo a redux-persist . O escribe el tuyo.
[ACTUALIZACIÓN 18 de diciembre de 2016] Editado para eliminar la mención de dos proyectos similares ahora inactivos o en desuso.
Si alguien tiene algún problema con las soluciones anteriores, puede escribir el suyo. Déjame mostrarte lo que hice. Ignorar las saga middleware
cosas solo se centran en dos cosas localStorageMiddleware
y reHydrateStore
métodos. el localStorageMiddleware
extrae todo redux state
y lo coloca local storage
y rehydrateStore
extrae todo el applicationState
almacenamiento local si está presente y lo coloca enredux 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
incluso cuando nada en la tienda ha cambiado? Cómo compensa las escrituras innecesarias
No puedo responder a @Gardezi, pero una opción basada en su código podría ser:
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 diferencia es que solo estamos guardando algunas acciones, podría utilizar una función antirrebote para guardar solo la última interacción de su estado
Llegué un poco tarde, pero implementé un estado persistente de acuerdo con los ejemplos aquí expuestos. Si desea actualizar el estado solo cada X segundos, este enfoque puede ayudarlo a:
Definir una función de contenedor
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!")
}
}
Llame a una función de contenedor en su suscriptor
store.subscribe((state) =>
{
updateLocalStorage(store.getState())
});
En este ejemplo, el estado se actualiza como máximo cada 5 segundos, independientemente de la frecuencia con que se active una actualización.
(state) => { updateLocalStorage(store.getState()) }
en lodash.throttle
este aspecto: store.subscribe(throttle(() => {(state) => { updateLocalStorage(store.getState())} }
y quitar tiempo comprobando el interior de la lógica.
Sobre la base de las excelentes sugerencias y extractos de código breve proporcionados en otras respuestas (y el artículo de Jam Creencia's Medium ), ¡aquí hay una solución completa!
Necesitamos un archivo que contenga 2 funciones que guarden / carguen el estado a / desde el almacenamiento local:
// 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;
}
};
Esas funciones son importadas por store.js donde configuramos nuestra tienda:
NOTA: Deberá agregar una dependencia: 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;
La tienda se importa a index.js para que se pueda pasar al proveedor que envuelve 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')
)
Tenga en cuenta que las importaciones absolutas requieren este cambio en YourProjectFolder / jsconfig.json : esto le indica dónde buscar archivos si no puede encontrarlos al principio. De lo contrario, verá quejas sobre el intento de importar algo desde fuera de src .
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}