Como posso persistir a árvore de estado redux na atualização?

92

O primeiro princípio da documentação Redux é:

O estado de todo o seu aplicativo é armazenado em uma árvore de objetos dentro de um único armazenamento.

E eu realmente pensei que entendia bem todos os princípios. Mas agora estou confuso sobre o que significa aplicação.

Se aplicação significa apenas uma pequena parte complicada no site e funciona em apenas uma página, eu entendo. Mas e se a aplicação significar um site inteiro? Devo usar LocalStorage ou cookie ou algo para manter a árvore de estado? mas e se o navegador não suportar LocalStorage?

Eu quero saber como os desenvolvedores mantêm sua árvore de estados! :)

incleaf
fonte
2
Essa é uma questão ampla. Você pode fazer qualquer uma das coisas que mencionou. Você tem um código que gostaria de compartilhar para nos mostrar o que tentou e não funcionou? Você pode implementar todo o seu site para ser uma única entidade ou pode ter várias. Você pode usar localStorage para persistir dados, ou um banco de dados real, ou nenhum. Aplicação significa instância viva e ativa. Na maioria dos casos, é apenas um, seu root. Mas, novamente, existem muitas maneiras de implementar aplicativos.
ZekeDroid de

Respostas:

88

Se você quiser manter seu estado redux em uma atualização do navegador, é melhor fazer isso usando middleware redux. Verifique o middleware redux-persist e redux-storage . Ambos tentam realizar a mesma tarefa de armazenar seu estado redux para que possa ser salvo e carregado à vontade.

-

Editar

Já faz algum tempo que não revi essa pergunta, mas vendo que a outra (embora a resposta mais votada) incentiva a apresentação de sua própria solução, decidi responder novamente.

A partir desta edição, ambas as bibliotecas foram atualizadas nos últimos seis meses. Minha equipe tem usado redux-persist em produção há alguns anos e não teve problemas.

Embora possa parecer um problema simples, você descobrirá rapidamente que implementar sua própria solução não apenas causará uma carga de manutenção, mas resultará em bugs e problemas de desempenho. Os primeiros exemplos que vêm à mente são:

  1. JSON.stringifye JSON.parsepode não apenas prejudicar o desempenho quando não é necessário, mas lançar erros que, quando não tratados em uma parte crítica do código, como sua loja redux, podem travar seu aplicativo.
  2. (Mencionado parcialmente na resposta abaixo): Descobrir quando e como salvar e restaurar o estado do seu aplicativo não é um problema simples. Faça isso com muita frequência e você prejudicará o desempenho. Não é o suficiente ou, se as partes erradas do estado persistirem, você poderá encontrar mais bugs. As bibliotecas mencionadas acima são testadas em batalha em sua abordagem e fornecem algumas maneiras bastante infalíveis de personalizar seu comportamento.
  3. Parte da beleza do redux (especialmente no ecossistema React) é sua capacidade de ser colocado em vários ambientes. A partir desta edição, redux-persist tem 15 implementações de armazenamento diferentes , incluindo a incrível biblioteca localForage para web, bem como suporte para React Native, Electron e Node.

Para resumir, para 3kB minificado + gzipado (no momento desta edição), este não é um problema que eu pediria à minha equipe para resolver sozinho.

michaelgmcd
fonte
5
Posso recomendar redux-persist (não tentei redux-storage ainda), mas funciona muito bem para mim com muito pouca configuração e instalação.
larrydahooster
A partir desta data, ambas as bibliotecas parecem estar mortas e não mantidas com os últimos commits há 2 anos.
AnBisw
1
parece que redux-persist está de volta um pouco, com uma nova publicação 22 dias atrás, no momento em que escrevi
Zeragamba
O novo local do redux-storage é github.com/react-stack/redux-storage
Michael Freidgeim
2
NOTE ISTO SOBRE ESTA RESPOSTA: A realidade é que o software e as bibliotecas geralmente adotam a abordagem baseada na comunidade (suporte) de que até mesmo alguns módulos muito importantes de uma linguagem de programação são suportados por terceiros / bibliotecas. Geralmente, o desenvolvedor deve ficar de olho em cada ferramenta usada em sua pilha para saber se está ficando obsoleta / atualizada ou não. Duas escolhas; 1. Implemente o seu próprio e continue desenvolvendo sempre garantindo padrões de desempenho e plataforma cruzada. 2. Use a solução testada em batalha e verifique apenas as atualizações / recomendações como @ MiFreidgeimSO-stopbeingevil diz
Geek Guy
80

Editar 25 de agosto de 2019

Conforme afirmado em um dos comentários. O pacote redux-storage original foi movido para react-stack . Essa abordagem ainda se concentra na implementação de sua própria solução de gerenciamento de estado.


Resposta Original

Embora a resposta fornecida seja válida em algum ponto, é importante notar que o pacote redux-storage original foi descontinuado e não está mais sendo mantido ...

O autor original do pacote redux-storage decidiu descontinuar o projeto e não o mantém mais.

Agora, se você não quiser depender de outros pacotes para evitar problemas como esses no futuro, é muito fácil lançar sua própria solução.

Tudo que você precisa fazer é:

1- Crie uma função que retorne o estado de localStoragee então passe o estado para a createStorefunção redux do segundo parâmetro para hidratar a loja

 const store = createStore(appReducers, state);

2- Ouça as mudanças de estado e toda vez que o estado mudar, salve o estado para localStorage

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

E é isso ... Na verdade, eu uso algo semelhante na produção, mas em vez de usar funções, escrevi uma classe muito simples como abaixo ...

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

e então ao inicializar seu aplicativo ...

import StateLoader from "./state.loader"

const stateLoader = new StateLoader();

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

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

Espero que ajude alguém

Nota de Desempenho

Se as alterações de estado forem muito frequentes em seu aplicativo, salvar no armazenamento local com muita frequência pode prejudicar o desempenho do aplicativo, especialmente se o gráfico do objeto de estado para serializar / desserializar for grande. Para esses casos, você pode querer debounce ou estrangular a função que salva estado para localStorage usando RxJs, lodashou algo similar.

Leo
fonte
11
Em vez de usar middleware, prefiro essa abordagem. Obrigado pelas dicas com relação à preocupação com o desempenho.
Joe zhou
1
Definitivamente, a resposta preferida. No entanto, quando eu atualizo a página e ela carrega o estado do armazenamento local quando cria a loja, recebo vários avisos que incluem o texto "Propriedades inesperadas [nomes de contêiner] encontradas no estado anterior recebidas pelo redutor. Esperado encontrar um dos em vez disso, nomes de propriedades redutoras conhecidas: "global", "idioma". Propriedades inesperadas serão ignoradas. Ainda funciona e basicamente reclama que, no momento da criação da loja, não conhece todos os outros contêineres. Existe um maneira de contornar este aviso?
Zief
@Zief difícil de dizer. A mensagem "parece" bastante clara, os redutores estão esperando propriedades que não são especificadas. Pode ser algo relacionado ao fornecimento de valores padrão para o estado serializado.
Leo
Solução muito simples. Obrigado.
Ishara Madawa
1
@Joezhou adoraria saber por que você prefere essa abordagem. Pessoalmente, parece ser exatamente para isso que o middleware se destina.
michaelgmcd
2

Isso é baseado na resposta de Leo (que deve ser a resposta aceita, uma vez que atinge o propósito da pergunta sem usar libs de terceiros).

Eu criei uma classe Singleton que cria um Redux Store, persiste usando armazenamento local e permite acesso simples ao seu armazenamento por meio de um getter .

Para usá-lo, basta colocar o seguinte elemento Redux-Provider em torno de sua classe principal:

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

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

e adicione a seguinte classe ao seu projeto:

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
fonte