web-dev-qa-db-fra.com

Où écrire sur localStorage dans une application Redux?

Je souhaite conserver certaines parties de mon arbre d'état dans le stockage local. Quel est le lieu approprié pour le faire? Réducteur ou action?

141
Marco de Jongh

Réducteur n'est jamais un endroit approprié pour cela car les réducteurs doivent être purs et ne pas avoir d'effets secondaires.

Je recommanderais simplement de le faire chez un abonné:

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

Avant de créer le magasin, lisez les 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 à l'aide de la valeur par défaut de leur argument state. Cela peut être très pratique.

Il est conseillé d’attaquer votre abonné afin de ne pas écrire trop vite sur localStorage, sinon vous aurez des problèmes de performances.

Enfin, vous pouvez créer un middleware qui l’encapsule à la place, mais je commencerais par un abonné car c’est une solution plus simple et qui fait bien le travail.

188
Dan Abramov

Pour compléter 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 tous les JSON sous votre clé comme ceci:

const persistedState = localStorage.getItem('reduxState') ? JSON.parse(localStorage.getItem('reduxState')) : {}

Vous passez ensuite cette constante peristedState à votre méthode createStore comme ceci:

const store = createStore(
  reducer, 
  persistedState,
  /* any middleware... */
)
135
Andrew Samuelsen

Dans un mot: middleware.

Départ redux-persist . Ou écrivez le vôtre.

[MISE À JOUR 18 décembre 2016] Édité pour supprimer la mention de deux projets similaires maintenant inactifs ou obsolètes.

43
David L. Walsh

Si vous rencontrez un problème avec les solutions ci-dessus, vous pouvez écrire le vôtre. Laisse-moi te montrer ce que j'ai fait. Ignorer saga middleware les choses se concentrent simplement sur deux choses localStorageMiddleware et reHydrateStore, méthode. la localStorageMiddleware tire tout le redux state et le met dans local storage et rehydrateStore tire tous les applicationState dans la mémoire locale si elle est présente et le met dans 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;
7
Gardezi

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 faisons que sauvegarder certaines actions. Vous pouvez même utiliser une fonction anti-rebond pour ne sauvegarder que la dernière interaction de votre état.

3
Douglas Caina

Je suis un peu en retard mais j'ai implémenté un état persistant selon les exemples donnés ici. Si vous souhaitez mettre à jour l'état uniquement toutes les X secondes, cette approche peut vous aider:

  1. 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!")
        }
    }
    
  2. Appeler une fonction wrapper dans votre abonné

        store.subscribe((state) =>
        {
        updateLocalStorage(store.getState())
         });
    

Dans cet exemple, l'état est mis à jour au plus toutes les 5 secondes, quelle que soit la fréquence à laquelle une mise à jour est déclenchée.

1
movcmpret