Comment puis-je conserver l'arborescence d'état de Redux lors de l'actualisation?

92

Le premier principe de la documentation Redux est:

L'état de l'ensemble de votre application est stocké dans une arborescence d'objets dans un seul magasin.

Et je pensais en fait que je comprenais bien tous les directeurs. Mais je ne sais plus ce que signifie l'application.

Si l'application ne signifie qu'une partie peu compliquée du site Web et fonctionne sur une seule page, je comprends. Mais que faire si l'application signifie tout le site Web? Dois-je utiliser LocalStorage ou cookie ou quelque chose pour conserver l'arborescence d'état? mais que faire si le navigateur ne prend pas en charge LocalStorage?

Je veux savoir comment les développeurs conservent leur arbre d'état! :)

incleaf
la source
2
C'est une question générale. Vous pouvez faire n'importe laquelle des choses que vous avez mentionnées. Avez-vous du code que vous aimeriez partager pour nous montrer ce que vous avez essayé et ce que vous n'avez pas fonctionné? Vous pouvez implémenter l'ensemble de votre site Web en une seule entité, ou vous pouvez en avoir plusieurs. Vous pouvez utiliser localStorage pour conserver les données, ou une vraie base de données, ou ni l'un ni l'autre. L'application signifie une instance vivante et active. Dans la plupart des cas, ce n'est qu'un seul, votre racine. Mais, encore une fois, il existe de nombreuses façons de mettre en œuvre des applications.
ZekeDroid

Réponses:

88

Si vous souhaitez conserver votre état redux lors d'une actualisation du navigateur, il est préférable de le faire à l'aide du middleware redux. Découvrez le middleware redux-persist et redux-storage . Ils essaient tous les deux d'accomplir la même tâche de stocker votre état redux afin qu'il puisse être sauvegardé et chargé à volonté.

-

Éditer

Cela faisait un certain temps que je n'avais pas revu cette question, mais voyant que l'autre (bien que la réponse plus votée) encourage le lancement de votre propre solution, j'ai pensé que je répondrais à nouveau.

À partir de cette modification, les deux bibliothèques ont été mises à jour au cours des six derniers mois. Mon équipe utilise redux-persist en production depuis quelques années maintenant et n'a eu aucun problème.

Bien que cela puisse sembler un problème simple, vous constaterez rapidement que le déploiement de votre propre solution entraînera non seulement une charge de maintenance, mais entraînera également des bogues et des problèmes de performances. Les premiers exemples qui me viennent à l'esprit sont:

  1. JSON.stringifyet JSON.parsepeut non seulement nuire aux performances lorsqu'il n'est pas nécessaire, mais générer des erreurs qui, lorsqu'elles ne sont pas gérées dans un morceau de code critique comme votre magasin redux, peuvent planter votre application.
  2. (Partiellement mentionné dans la réponse ci-dessous): déterminer quand et comment enregistrer et restaurer l'état de votre application n'est pas un problème simple. Faites-le trop souvent et vous nuirez aux performances. Pas assez, ou si les mauvaises parties de l'état persistent, vous pouvez vous retrouver avec plus de bogues. Les bibliothèques mentionnées ci-dessus sont testées au combat dans leur approche et fournissent des moyens assez infaillibles de personnaliser leur comportement.
  3. Une partie de la beauté de redux (en particulier dans l'écosystème React) est sa capacité à être placé dans plusieurs environnements. À partir de cette modification, redux-persist dispose de 15 implémentations de stockage différentes , y compris l'impressionnante bibliothèque localForage pour le Web, ainsi que la prise en charge de React Native, Electron et Node.

Pour résumer, pour 3 Ko minifiés + gzippés (au moment de cette édition) ce n'est pas un problème que je demanderais à mon équipe de se résoudre tout seul.

michaelgmcd
la source
5
Je peux recommander redux-persist (je n'ai pas encore essayé redux-storage) mais cela fonctionne plutôt bien pour moi avec très peu de configuration et d'installation.
larrydahooster
À partir de cette date, les deux bibliothèques semblent être mortes et ne pas être maintenues avec les derniers validations il y a 2 ans.
AnBisw
1
ressemble à redux-persist est un peu de retour, avec une nouvelle publication il y a 22 jours au moment de la rédaction
Zeragamba
Le nouvel emplacement de redux-storage est github.com/react-stack/redux-storage
Michael Freidgeim
2
NOTEZ CECI À PROPOS DE CETTE RÉPONSE: La réalité est que les logiciels et bibliothèques ont généralement adopté une approche communautaire (support) selon laquelle même certains modules très importants d'un langage de programmation sont pris en charge par des tiers / bibliothèques. Généralement, le développeur doit garder un œil sur chaque outil utilisé dans sa pile pour savoir s'il devient obsolète / mis à jour ou non. Deux choix; 1. Mettez en œuvre le vôtre et continuez à vous développer pour toujours en garantissant des performances et des normes multiplateformes. 2. Utilisez une solution testée au combat et ne vérifiez que les mises à jour / recommandations comme le dit @ MiFreidgeimSO-stopbeingevil
Geek Guy
80

Edit 25-août-2019

Comme indiqué dans l'un des commentaires. Le package d' origine redux-storage a été déplacé vers react-stack . Cette approche se concentre toujours sur la mise en œuvre de votre propre solution de gestion d'état.


Réponse originale

Bien que la réponse fournie était valide à un moment donné, il est important de noter que le package redux-storage d'origine a été abandonné et qu'il n'est plus maintenu ...

L'auteur original du paquet redux-storage a décidé de rendre le projet obsolète et n'est plus maintenu.

Maintenant, si vous ne voulez pas avoir de dépendances sur d'autres packages pour éviter des problèmes comme ceux-ci à l'avenir, il est très facile de déployer votre propre solution.

Tout ce que vous avez à faire est:

1- Créer une fonction qui retourne l'état de localStoragepuis passe l'état à la createStorefonction redux de s dans le deuxième paramètre afin d'hydrater le magasin

 const store = createStore(appReducers, state);

2- Écoutez les changements d'état et à chaque fois que l'état change, enregistrez l'état dans localStorage

store.subscribe(() => {
    //this is just a function that saves state to localStorage
    saveState(store.getState());
}); 

Et c'est tout ... J'utilise en fait quelque chose de similaire en production, mais au lieu d'utiliser des fonctions, j'ai écrit une classe très simple comme ci-dessous ...

class StateLoader {

    loadState() {
        try {
            let serializedState = localStorage.getItem("http://contoso.com:state");

            if (serializedState === null) {
                return this.initializeState();
            }

            return JSON.parse(serializedState);
        }
        catch (err) {
            return this.initializeState();
        }
    }

    saveState(state) {
        try {
            let serializedState = JSON.stringify(state);
            localStorage.setItem("http://contoso.com:state", serializedState);

        }
        catch (err) {
        }
    }

    initializeState() {
        return {
              //state object
            }
        };
    }
}

puis lors du démarrage de votre application ...

import StateLoader from "./state.loader"

const stateLoader = new StateLoader();

let store = createStore(appReducers, stateLoader.loadState());

store.subscribe(() => {
    stateLoader.saveState(store.getState());
});

J'espère que ça aide quelqu'un

Note de performance

Si les changements d'état sont très fréquents dans votre application, l'enregistrement trop fréquent sur le stockage local peut nuire aux performances de votre application, en particulier si le graphique d'objet d'état à sérialiser / désérialiser est volumineux. Pour ces cas, vous voudrez peut - être debounce ou accélérateur de la fonction qui permet d' économiser à l' état localStorage en utilisant RxJs, lodashou quelque chose de similaire.

Leo
la source
11
Au lieu d'utiliser un middleware, je préfère cette approche. Merci pour les conseils concernant le souci de performance.
Joe zhou
1
Certainement la réponse préférée. Cependant, lorsque j'actualise la page et qu'il charge l'état à partir du stockage local lors de la création du magasin, j'obtiens plusieurs avertissements qui incluent le texte "Propriétés inattendues [noms de conteneur] trouvées dans l'état précédent reçu par le réducteur. On s'attend à trouver l'un des à la place des noms de propriété de réducteur connus: "global", "language". Les propriétés inattendues seront ignorées. Cela fonctionne toujours, et se plaint essentiellement qu'au moment de la création du magasin, il ne connaît pas tous ces autres conteneurs. Y a-t-il un contourner cet avertissement?
Zief
@Zief difficile à dire. Le message "semble" assez clair, les réducteurs attendent des propriétés qui ne sont pas spécifiées. Cela pourrait être quelque chose lié à la fourniture de valeurs par défaut à l'état sérialisé?
Leo
Solution très simple. Merci.
Ishara Madawa
1
@Joezhou aimerait savoir pourquoi vous préférez cette approche. Personnellement, cela semble être exactement ce à quoi le middleware était destiné.
michaelgmcd
1

Ceci est basé sur la réponse de Leo (qui devrait être la réponse acceptée car elle atteint le but de la question sans utiliser de bibliothèques tierces).

J'ai créé une classe Singleton qui crée un magasin Redux, le persiste en utilisant le stockage local et permet un accès simple à son magasin via un getter .

Pour l'utiliser, placez simplement l'élément Redux-Provider suivant autour de votre classe principale:

// ... Your other imports
import PersistedStore from "./PersistedStore";

ReactDOM.render(
  <Provider store={PersistedStore.getDefaultStore().store}>
    <MainClass />
  </Provider>,
  document.getElementById('root')
);

et ajoutez la classe suivante à votre projet:

import {
  createStore
} from "redux";

import rootReducer from './RootReducer'

const LOCAL_STORAGE_NAME = "localData";

class PersistedStore {

  // Singleton property
  static DefaultStore = null;

  // Accessor to the default instance of this class
  static getDefaultStore() {
    if (PersistedStore.DefaultStore === null) {
      PersistedStore.DefaultStore = new PersistedStore();
    }

    return PersistedStore.DefaultStore;
  }

  // Redux store
  _store = null;

  // When class instance is used, initialize the store
  constructor() {
    this.initStore()
  }

  // Initialization of Redux Store
  initStore() {
    this._store = createStore(rootReducer, PersistedStore.loadState());
    this._store.subscribe(() => {
      PersistedStore.saveState(this._store.getState());
    });
  }

  // Getter to access the Redux store
  get store() {
    return this._store;
  }

  // Loading persisted state from localStorage, no need to access
  // this method from the outside
  static loadState() {
    try {
      let serializedState = localStorage.getItem(LOCAL_STORAGE_NAME);

      if (serializedState === null) {
        return PersistedStore.initialState();
      }

      return JSON.parse(serializedState);
    } catch (err) {
      return PersistedStore.initialState();
    }
  }

  // Saving persisted state to localStorage every time something
  // changes in the Redux Store (This happens because of the subscribe() 
  // in the initStore-method). No need to access this method from the outside
  static saveState(state) {
    try {
      let serializedState = JSON.stringify(state);
      localStorage.setItem(LOCAL_STORAGE_NAME, serializedState);
    } catch (err) {}
  }

  // Return whatever you want your initial state to be
  static initialState() {
    return {};
  }
}

export default PersistedStore;

thgc
la source