¿Cómo puedo conservar el árbol de estado redux en la actualización?

92

El primer principio de la documentación de Redux es:

El estado de toda su aplicación se almacena en un árbol de objetos dentro de una sola tienda.

Y de hecho pensé que comprendo bien todos los principios. Pero ahora estoy confundido sobre qué significa la aplicación.

Si la aplicación significa solo una parte poco complicada en el sitio web y funciona en una sola página, lo entiendo. Pero, ¿y si aplicación significa sitio web completo? ¿Debo usar LocalStorage o cookie o algo para mantener el árbol del estado? pero ¿qué pasa si el navegador no es compatible con LocalStorage?

¡Quiero saber cómo los desarrolladores mantienen su árbol de estado! :)

incleaf
fuente
2
Esa es una pregunta amplia. Puede hacer cualquiera de las cosas que mencionó. ¿Tiene un código que le gustaría compartir para mostrarnos lo que intentó y no funcionó? Puede implementar todo su sitio web para que sea una sola entidad, o puede tener varios. Puede usar localStorage para conservar datos, o una base de datos real, o ninguno. Aplicación significa instancia viva y activa. En la mayoría de los casos, este es solo uno, su raíz. Pero, nuevamente, hay muchas formas de implementar aplicaciones.
ZekeDroid

Respuestas:

88

Si desea mantener su estado redux a través de una actualización del navegador, es mejor hacerlo usando el middleware redux. Echa un vistazo a la redux-persistir y redux de almacenamiento intermedio. Ambos intentan realizar la misma tarea de almacenar su estado redux para que pueda guardarse y cargarse a voluntad.

-

Editar

Ha pasado algún tiempo desde que revisé esta pregunta, pero al ver que la otra (aunque la respuesta más votada a favor) alienta a lanzar su propia solución, pensé que respondería esto nuevamente.

A partir de esta edición, ambas bibliotecas se han actualizado en los últimos seis meses. Mi equipo ha estado usando redux-persist en producción durante algunos años y no ha tenido problemas.

Si bien puede parecer un problema simple, rápidamente descubrirá que implementar su propia solución no solo causará una carga de mantenimiento, sino que también generará errores y problemas de rendimiento. Los primeros ejemplos que me vienen a la mente son:

  1. JSON.stringifyy JSON.parseno solo puede dañar el rendimiento cuando no es necesario, sino también generar errores que, cuando no se manejan en una parte crítica del código, como su tienda redux, pueden bloquear su aplicación.
  2. (Parcialmente mencionado en la respuesta a continuación): averiguar cuándo y cómo guardar y restaurar el estado de su aplicación no es un problema simple. Hágalo con demasiada frecuencia y perjudicará el rendimiento. No es suficiente, o si persisten las partes equivocadas del estado, es posible que se encuentre con más errores. Las bibliotecas mencionadas anteriormente se prueban en batalla en su enfoque y brindan algunas formas bastante infalibles de personalizar su comportamiento.
  3. Parte de la belleza de redux (especialmente en el ecosistema React) es su capacidad para colocarse en múltiples entornos. A partir de esta edición, redux-persist tiene 15 implementaciones de almacenamiento diferentes , incluida la increíble biblioteca localForage para web, así como soporte para React Native, Electron y Node.

En resumen, para 3kB minificado + gzip (en el momento de esta edición) esto no es un problema, le pediría a mi equipo que lo resuelva solo.

michaelgmcd
fuente
5
Puedo recomendar redux-persist (todavía no he probado redux-storage) pero funciona bastante bien para mí con muy poca configuración y configuración.
larrydahooster
A partir de esta fecha, ambas bibliotecas parecen estar muertas y no se mantienen con las últimas confirmaciones de hace 2 años.
AnBisw
1
parece que redux-persist ha vuelto un poco, con una nueva publicación hace 22 días en el momento de escribir este artículo
Zeragamba
La nueva ubicación de redux-storage es github.com/react-stack/redux-storage
Michael Freidgeim
2
TENGA EN CUENTA ESTO SOBRE ESTA RESPUESTA: La realidad es que el software y las bibliotecas generalmente han adoptado un enfoque basado en la comunidad (soporte) que incluso algunos módulos muy importantes de un lenguaje de programación son compatibles con terceros / bibliotecas. Generalmente, el desarrollador tiene que estar atento a todas las herramientas utilizadas en su pila para saber si se está desaprobando / actualizando o no. Dos opciones; 1. Implemente el suyo y siga desarrollándose para siempre, garantizando el rendimiento y los estándares multiplataforma. 2. Use una solución probada en batalla y solo verifique las actualizaciones / recomendaciones como dice @ MiFreidgeimSO-stopbeingevil
Geek Guy
80

Editar 25-ago-2019

Como se indica en uno de los comentarios. El paquete original de redux-storage se ha movido a react-stack . Este enfoque aún se enfoca en implementar su propia solución de administración de estado.


Respuesta original

Si bien la respuesta proporcionada fue válida en algún momento, es importante notar que el paquete original redux-storage ha quedado obsoleto y ya no se mantiene ...

El autor original del paquete redux-storage ha decidido desaprobar el proyecto y dejar de mantenerlo.

Ahora bien, si no desea tener dependencias en otros paquetes para evitar problemas como estos en el futuro, es muy fácil lanzar su propia solución.

Todo lo que necesitas hacer es:

1- Cree una función que devuelva el estado desde localStoragey luego pase el estado a la función createStore's redux en el segundo parámetro para hidratar la tienda

 const store = createStore(appReducers, state);

2- Escuche los cambios de estado y cada vez que cambie el estado, guarde el estado para localStorage

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

Y eso es todo ... De hecho, uso algo similar en producción, pero en lugar de usar funciones, escribí una clase muy simple como la siguiente ...

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
            }
        };
    }
}

y luego al arrancar su aplicación ...

import StateLoader from "./state.loader"

const stateLoader = new StateLoader();

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

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

Espero que ayude a alguien

Nota de rendimiento

Si los cambios de estado son muy frecuentes en su aplicación, guardar en el almacenamiento local con demasiada frecuencia podría dañar el rendimiento de su aplicación, especialmente si el gráfico de objetos de estado para serializar / deserializar es grande. Para estos casos, es posible que desee eliminar el rebote o acelerar la función que guarda el estado en localStorage usando RxJs, lodasho algo similar.

León
fuente
11
En lugar de usar middleware, prefiero este enfoque. Gracias por los consejos relacionados con el rendimiento.
Joe zhou
1
Definitivamente la respuesta preferida. Sin embargo, cuando actualizo la página y carga el estado del almacenamiento local cuando crea la tienda, recibo varias advertencias que incluyen el texto "Propiedades inesperadas [nombres de contenedores] encontradas en el estado anterior recibido por el reductor. Se espera encontrar uno de los nombres conocidos de propiedades reductoras en su lugar: "global", "language". Las propiedades inesperadas se ignorarán. Todavía funciona, y básicamente se queja de que en el momento de crear la tienda no conoce todos esos otros contenedores. ¿Hay un ¿Cómo evitar esta advertencia?
Zief
@Zief es difícil de decir. El mensaje "parece" bastante claro, los reductores esperan propiedades que no están especificadas. ¿Podría ser algo relacionado con proporcionar valores predeterminados al estado serializado?
Leo
Solución muy sencilla. Gracias.
Ishara Madawa
1
A @Joezhou le encantaría saber por qué prefiere este enfoque. Personalmente, esto parece exactamente para lo que estaba destinado el middleware.
michaelgmcd
1

Esto se basa en la respuesta de Leo (que debería ser la respuesta aceptada, ya que logra el propósito de la pregunta sin usar bibliotecas de terceros).

Creé una clase Singleton que crea una tienda Redux, la persiste usando el almacenamiento local y permite un acceso simple a su tienda a través de un captador .

Para usarlo, simplemente coloque el siguiente elemento Redux-Provider alrededor de su clase principal:

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

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

y agregue la siguiente clase a su proyecto:

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
fuente