Je souhaite conserver certaines parties de mon arborescence d'état dans le localStorage. Quel est le lieu approprié pour le faire? Réducteur ou action?
Je souhaite conserver certaines parties de mon arborescence d'état dans le localStorage. Quel est le lieu approprié pour le faire? Réducteur ou action?
Réponses:
Le réducteur n'est jamais un endroit approprié pour le faire, car les réducteurs doivent être purs et ne pas avoir d'effets secondaires.
Je recommanderais simplement de le faire dans un abonné:
store.subscribe(() => {
// persist your state
})
Avant de créer le magasin, lisez ces parties persistantes:
const persistedState = // ...
const store = createStore(reducer, persistedState)
Si vous utilisez, combineReducers()
vous remarquerez que les réducteurs qui n'ont pas reçu l'état «démarreront» normalement en utilisant leur valeur d' state
argument par défaut . Cela peut être très pratique.
Il est conseillé de supprimer votre abonné afin de ne pas écrire trop rapidement dans localStorage, sinon vous aurez des problèmes de performances.
Enfin, vous pouvez créer un middleware qui encapsule cela comme une alternative, mais je commencerais par un abonné car c'est une solution plus simple et fait bien le travail.
Pour remplir les blancs de la réponse de Dan Abramov, vous pouvez utiliser store.subscribe()
comme ceci:
store.subscribe(()=>{
localStorage.setItem('reduxState', JSON.stringify(store.getState()))
})
Avant de créer le magasin, vérifiez localStorage
et analysez tout JSON sous votre clé comme ceci:
const persistedState = localStorage.getItem('reduxState')
? JSON.parse(localStorage.getItem('reduxState'))
: {}
Vous passez ensuite cette persistedState
constante à votre createStore
méthode comme ceci:
const store = createStore(
reducer,
persistedState,
/* any middleware... */
)
persistedState
retourner initialState
au lieu d'un objet vide? Sinon, je pense que createStore
va initialiser avec cet objet vide.
En un mot: middleware.
Découvrez redux-persist . Ou écrivez le vôtre.
[UPDATE 18 Dec 2016] Modifié pour supprimer la mention de deux projets similaires désormais inactifs ou obsolètes.
Si quelqu'un a un problème avec les solutions ci-dessus, vous pouvez écrire le vôtre. Laissez-moi vous montrer ce que j'ai fait. Ignorez les saga middleware
choses, concentrez-vous simplement sur deux choses localStorageMiddleware
et la reHydrateStore
méthode. le localStorageMiddleware
tirer tout le redux state
et le met dedans local storage
et rehydrateStore
tirez tout le applicationState
dans le stockage local si présent et le met dedansredux 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
même si rien dans le magasin n'a changé? Comment compenser les écritures inutiles
Je ne peux pas répondre à @Gardezi mais une option basée sur son code pourrait être:
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 différence est que nous ne sauvegardons que quelques actions, vous pouvez éventuellement utiliser une fonction anti-rebond pour ne sauvegarder que la dernière interaction de votre état
Je suis un peu en retard mais j'ai implémenté un état persistant selon les exemples cités ici. Si vous souhaitez mettre à jour l'état uniquement toutes les X secondes, cette approche peut vous aider:
Définir une fonction 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!")
}
}
Appeler une fonction wrapper dans votre abonné
store.subscribe((state) =>
{
updateLocalStorage(store.getState())
});
Dans cet exemple, l'état est mis à jour au maximum toutes les 5 secondes, quelle que soit la fréquence à laquelle une mise à jour est déclenchée.
(state) => { updateLocalStorage(store.getState()) }
dans lodash.throttle
comme ceci: store.subscribe(throttle(() => {(state) => { updateLocalStorage(store.getState())} }
et de supprimer la logique de vérification de temps à l' intérieur.
S'appuyant sur les excellentes suggestions et extraits de code courts fournis dans d'autres réponses (et l'article Medium de Jam Creencia ), voici une solution complète!
Nous avons besoin d'un fichier contenant 2 fonctions qui sauvegardent / chargent l'état vers / depuis le stockage 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;
}
};
Ces fonctions sont importées par store.js où nous configurons notre boutique:
REMARQUE: vous devrez ajouter une dépendance: 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;
Le magasin est importé dans index.js afin qu'il puisse être transmis au fournisseur qui encapsule 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')
)
Notez que les importations absolues nécessitent cette modification de YourProjectFolder / jsconfig.json - cela lui indique où chercher les fichiers s'il ne peut pas les trouver au début. Sinon, vous verrez des plaintes concernant la tentative d'importer quelque chose de l'extérieur de src .
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}