Jak mogę zachować drzewo stanu Redux podczas odświeżania?

92

Pierwszą zasadą dokumentacji Redux jest:

Stan całej aplikacji jest przechowywany w drzewie obiektów w jednym magazynie.

Pomyślałem, że dobrze rozumiem wszystkie zasady. Ale teraz nie wiem, co oznacza aplikacja.

Jeśli aplikacja to tylko jedna z mało skomplikowanych części witryny i działa tylko na jednej stronie, rozumiem. A co, jeśli aplikacja oznacza całą witrynę? Czy powinienem używać LocalStorage lub cookie, czy czegoś innego do utrzymania drzewa stanu? ale co, jeśli przeglądarka nie obsługuje LocalStorage?

Chcę wiedzieć, jak deweloperzy zachowują swoje drzewo stanów! :)

incleaf
źródło
2
To dość szerokie pytanie. Możesz zrobić dowolną z wymienionych rzeczy. Czy masz kod, którym chcesz się podzielić, aby pokazać nam, co wypróbowałeś, a nie zadziałało? Możesz zaimplementować całą witrynę internetową jako pojedynczą jednostkę lub możesz mieć wiele. Możesz użyć localStorage do utrwalania danych, prawdziwej bazy danych lub żadnego z nich. Aplikacja to żywa, aktywna instancja. W większości przypadków jest to tylko jeden, twój root. Ale znowu istnieje wiele sposobów wdrażania aplikacji.
ZekeDroid

Odpowiedzi:

88

Jeśli chcesz zachować swój stan Redux podczas odświeżania przeglądarki, najlepiej to zrobić za pomocą oprogramowania pośredniczącego Redux. Sprawdź oprogramowanie pośredniczące redux-persist i redux-storage . Obaj próbują wykonać to samo zadanie, czyli przechowywać stan redux, aby można go było zapisywać i ładować do woli.

-

Edytować

Minęło trochę czasu, odkąd wróciłem do tego pytania, ale widząc, że druga (choć bardziej pozytywna odpowiedź) zachęca do wprowadzenia własnego rozwiązania, pomyślałem, że odpowiem ponownie.

Od tej edycji obie biblioteki zostały zaktualizowane w ciągu ostatnich sześciu miesięcy. Mój zespół od kilku lat używa Redux-persist w produkcji i nie miał żadnych problemów.

Chociaż może się to wydawać prostym problemem, szybko przekonasz się, że wprowadzenie własnego rozwiązania nie tylko spowoduje obciążenie konserwacyjne, ale także spowoduje błędy i problemy z wydajnością. Pierwsze przychodzące na myśl przykłady to:

  1. JSON.stringifyi JSON.parsemoże nie tylko obniżyć wydajność, gdy nie jest potrzebna, ale także generować błędy, które nieobsługiwane w krytycznym fragmencie kodu, takim jak sklep redux, mogą spowodować awarię aplikacji.
  2. (Częściowo wspomniane w odpowiedzi poniżej): Ustalenie, kiedy i jak zapisać i przywrócić stan aplikacji, nie jest prostym problemem. Rób to zbyt często, a pogorszysz wydajność. Za mało lub jeśli utrzymują się niewłaściwe części stanu, możesz znaleźć więcej błędów. Biblioteki wymienione powyżej są testowane w walce w swoim podejściu i zapewniają całkiem niezawodny sposób dostosowywania ich zachowania.
  3. Częścią piękna Reduxu (szczególnie w ekosystemie React) jest możliwość umieszczenia go w wielu środowiskach. Od tej edycji redux-persist ma 15 różnych implementacji pamięci , w tym niesamowitą bibliotekę localForage dla sieci, a także wsparcie dla React Native, Electron i Node.

Podsumowując, dla 3kB minified + gzipped (w czasie tej edycji) nie jest to problem, który prosiłbym mój zespół o samodzielne rozwiązanie.

michaelgmcd
źródło
5
Mogę polecić redux-persist (jeszcze nie próbowałem redux-storage), ale działa całkiem dobrze dla mnie z bardzo małą konfiguracją i ustawieniami.
larrydahooster
Od tego dnia obie biblioteki wydają się być martwe i nieobsługiwane, a ostatnie zmiany sięgają 2 lat temu.
AnBisw
1
wygląda jak Redux-utrzymują powraca nieco z nowym opublikować 22 dni temu w czasie mojego pisania
Zeragamba
Nowa lokalizacja magazynu redux
Michael Freidgeim
2
UWAGA NA TEMAT TEJ ODPOWIEDZI: Rzeczywistość jest taka, że ​​oprogramowanie i biblioteki ogólnie przyjęły podejście oparte na społeczności (wsparciu), że nawet niektóre bardzo ważne moduły języka programowania są obsługiwane przez strony trzecie / biblioteki. Ogólnie rzecz biorąc, programista musi obserwować każde narzędzie używane w swoim stosie, aby wiedzieć, czy jest przestarzałe / aktualizowane, czy nie. Dwie możliwości; 1. Wdrażaj własne i nieustannie rozwijaj, zapewniając wydajność i standardy międzyplatformowe. 2. Zastosowanie bitwa sprawdzone rozwiązania i sprawdzić tylko Aktualizacje / zalecenia @ MiFreidgeimSO-stopbeingevil mówi
Geek Guy
80

Edytuj 25 sierpnia 2019 r

Jak stwierdzono w jednym z komentarzy. Oryginalny pakiet Redux-Storage został przeniesiony do React -stack . To podejście nadal koncentruje się na wdrażaniu własnego rozwiązania do zarządzania stanem.


Oryginalna odpowiedź

Chociaż udzielona odpowiedź była prawidłowa w pewnym momencie, należy zauważyć, że oryginalny pakiet redux-storage został przestarzały i nie jest już obsługiwany ...

Oryginalny autor pakietu redux-storage zdecydował o wycofaniu projektu i zaprzestaniu jego obsługi.

Teraz, jeśli nie chcesz mieć zależności od innych pakietów, aby uniknąć takich problemów w przyszłości, bardzo łatwo jest stworzyć własne rozwiązanie.

Wystarczy, że:

1- Utwórz funkcję, która zwraca stan z, localStoragea następnie przekazuje stan do createStorefunkcji redux w drugim parametrze w celu nawodnienia sklepu

 const store = createStore(appReducers, state);

2- Nasłuchuj zmian stanu i za każdym razem, gdy stan się zmienia, zapisz stan localStorage

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

I to wszystko ... Właściwie używam czegoś podobnego w produkcji, ale zamiast korzystać z funkcji napisałem bardzo prostą klasę jak poniżej ...

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

a następnie podczas ładowania aplikacji ...

import StateLoader from "./state.loader"

const stateLoader = new StateLoader();

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

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

Mam nadzieję, że to komuś pomoże

Uwaga dotycząca wydajności

Jeśli zmiany stanu są bardzo częste w aplikacji, zbyt częste zapisywanie w magazynie lokalnym może zaszkodzić wydajności aplikacji, zwłaszcza jeśli wykres obiektu stanu do serializacji / deserializacji jest duży. W takich przypadkach warto opóźnieniu na przepustnicy lub funkcję, która zapisuje stan na localStorage użyciu RxJs, lodashlub coś podobnego.

Lew
źródło
11
Zamiast korzystać z oprogramowania pośredniego, wolę takie podejście. Dzięki za wskazówki dotyczące troski o wydajność.
Joe zhou
1
Zdecydowanie preferowana odpowiedź. Jednak kiedy odświeżam stronę i ładuje stan z localstorage podczas tworzenia sklepu, pojawia się kilka ostrzeżeń, które zawierają tekst „Nieoczekiwane właściwości [nazwy kontenerów] znalezione w poprzednim stanie odebranym przez reduktor. Oczekiwano znalezienia jednego z zamiast znanych nazw właściwości reduktora: „globalne”, „język”. Nieoczekiwane właściwości zostaną zignorowane. Nadal działa i zasadniczo narzeka, że ​​w momencie tworzenia sklepu nie ma informacji o wszystkich innych kontenerach. Czy istnieje sposób obejścia tego ostrzeżenia?
Zief
@Zief trudno powiedzieć. Komunikat „wydaje się” całkiem jasny, reduktory oczekują właściwości, które nie zostały określone. Może to być coś związanego z dostarczaniem wartości domyślnych do stanu serializacji?
Leo
Bardzo proste rozwiązanie. Dziękuję Ci.
Ishara Madawa,
1
@Joezhou chciałbym usłyszeć, dlaczego wolisz to podejście. Osobiście wydaje się, że to właśnie oprogramowanie pośredniczące było przeznaczone.
michaelgmcd
1

Jest to oparte na odpowiedzi Leo (która powinna być zaakceptowaną odpowiedzią, ponieważ osiąga cel pytania bez korzystania z bibliotek innych firm).

Stworzyłem klasę Singleton, która tworzy Redux Store, utrzymuje go przy użyciu lokalnej pamięci i umożliwia prosty dostęp do swojego magazynu poprzez getter .

Aby go użyć, po prostu umieść następujący element Redux-Provider wokół swojej głównej klasy:

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

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

i dodaj następującą klasę do swojego projektu:

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
źródło