Как я могу сохранить дерево состояний редукции при обновлении?

92

Первый принцип документации Redux:

Состояние всего вашего приложения хранится в дереве объектов в одном хранилище.

И мне действительно казалось, что я хорошо понимаю всех руководителей. Но теперь я не понимаю, что означает приложение.

Я понимаю, если приложение означает лишь одну из небольших сложных частей на сайте и работает только на одной странице. Но что, если приложение означает весь сайт? Должен ли я использовать LocalStorage или cookie или что-то еще для сохранения дерева состояний? но что, если браузер не поддерживает LocalStorage?

Я хочу знать, как разработчики хранят свое дерево состояний! :)

incleaf
источник
2
Это довольно широкий вопрос. Вы можете делать все, что вы упомянули. У вас есть код, которым вы хотели бы поделиться, чтобы показать нам, что вы пробовали и что не сработало? Вы можете реализовать весь свой веб-сайт как единое целое, или у вас может быть несколько. Вы можете использовать localStorage для сохранения данных или настоящую БД, или ни то, ни другое. Приложение означает живой, активный экземпляр. В большинстве случаев это только один, ваш корень. Но, опять же, есть много способов реализовать приложения.
ZekeDroid

Ответы:

88

Если вы хотите сохранить свое состояние redux при обновлении браузера, лучше всего сделать это с помощью промежуточного программного обеспечения redux. Ознакомьтесь с промежуточным программным обеспечением redux-persist и redux-storage . Они оба пытаются выполнить одну и ту же задачу по сохранению вашего состояния redux, чтобы его можно было сохранять и загружать по желанию.

-

редактировать

Прошло некоторое время с тех пор, как я вернулся к этому вопросу, но, увидев, что другой (хотя и более одобренный ответ) поощряет развертывание вашего собственного решения, я решил, что отвечу на это еще раз.

На момент редактирования обе библиотеки были обновлены в течение последних шести месяцев. Моя команда уже несколько лет использует redux-persist в продакшене и не имеет никаких проблем.

Хотя это может показаться простой проблемой, вы быстро обнаружите, что развертывание собственного решения не только вызовет бремя обслуживания, но и приведет к ошибкам и проблемам с производительностью. Первые примеры, которые приходят на ум:

  1. JSON.stringifyи JSON.parseможет не только снизить производительность, когда он не нужен, но и вызвать ошибки, которые, если их не обработать в критически важном фрагменте кода, таком как ваше хранилище redux, могут привести к сбою вашего приложения.
  2. (Частично упомянуто в ответе ниже): выяснить, когда и как сохранить и восстановить состояние вашего приложения, - непростая задача. Делайте это слишком часто, и вы снизите производительность. Недостаточно, или если сохранятся неправильные части состояния, вы можете столкнуться с большим количеством ошибок. Упомянутые выше библиотеки испытаны в боевых условиях по своему подходу и предоставляют несколько довольно надежных способов настройки их поведения.
  3. Отчасти преимущество redux (особенно в экосистеме React) заключается в его способности размещать в нескольких средах. На момент редактирования в redux-persist было 15 различных реализаций хранилища , включая потрясающую библиотеку localForage для Интернета, а также поддержку React Native, Electron и Node.

Подводя итог, для 3kB minified + gzip (на момент редактирования) это не проблема, я бы попросил мою команду решить саму себя.

michaelgmcd
источник
5
Я могу порекомендовать redux-persist (еще не пробовал redux-storage), но для меня он работает очень хорошо с очень небольшой конфигурацией и настройкой.
larrydahooster
По состоянию на эту дату обе библиотеки считаются мертвыми и не поддерживаются с последними фиксациями еще 2 года назад.
AnBisw
1
похоже, что redux-persist немного вернулся, с новой публикацией 22 дня назад на момент написания
Zeragamba
Новое местоположение redux-storage - github.com/react-stack/redux-storage
Майкл
2
ОБРАТИТЕ ВНИМАНИЕ ОБ ЭТОМ ОТВЕТЕ: Реальность такова, что программное обеспечение и библиотеки обычно используют подход, основанный на сообществе (поддержке), когда даже некоторые очень важные модули языка программирования поддерживаются третьими сторонами / библиотеками. Как правило, разработчик должен следить за каждым инструментом, используемым в его стеке, чтобы знать, устарел / обновлен он или нет. Два варианта; 1. Внедрите свой собственный продукт и продолжайте развиваться, обеспечивая производительность и кроссплатформенные стандарты. 2. Используйте проверенное в боях решение и проверяйте только обновления / рекомендации, как говорит @ MiFreidgeimSO-stopbeingevil
Geek Guy
80

Редактировать 25 августа 2019 г.

Как сказано в одном из комментариев. Исходный пакет redux-storage перемещен в response-stack . Этот подход по-прежнему ориентирован на реализацию вашего собственного решения для управления состоянием.


Оригинальный ответ

Хотя предоставленный ответ был действителен в какой-то момент, важно отметить, что исходный пакет redux-storage устарел и больше не поддерживается ...

Первоначальный автор пакета redux-storage решил отказаться от проекта и больше не поддерживается.

Теперь, если вы не хотите иметь зависимости от других пакетов, чтобы избежать подобных проблем в будущем, очень легко развернуть собственное решение.

Все, что вам нужно сделать, это:

1- Создайте функцию, которая возвращает состояние из, localStorageа затем передает состояние createStoreфункции redux во втором параметре, чтобы гидратировать хранилище

 const store = createStore(appReducers, state);

2- Слушайте изменения состояния и каждый раз, когда состояние изменяется, сохраняйте состояние в localStorage

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

И все ... Я действительно использую что-то подобное в производстве, но вместо использования функций я написал очень простой класс, как показано ниже ...

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

а затем при загрузке вашего приложения ...

import StateLoader from "./state.loader"

const stateLoader = new StateLoader();

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

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

Надеюсь, это кому-то поможет

Примечание производительности

Если изменения состояния в вашем приложении происходят очень часто, слишком частое сохранение в локальное хранилище может снизить производительность вашего приложения, особенно если граф объекта состояния для сериализации / десериализации большой. В этих случаях вы можете захотеть дребезг или задушить функцию , которая сохраняет состояние в LocalStorage использование RxJs, lodashили что - то подобное.

Лео
источник
11
Я предпочитаю этот подход вместо использования промежуточного программного обеспечения. Спасибо за советы по поводу производительности.
Джо Чжоу
1
Определенно предпочтительный ответ. Однако, когда я обновляю страницу и она загружает состояние из localstorage при создании хранилища, я получаю несколько предупреждений, включающих текст «Неожиданные свойства [имена контейнеров], найденные в предыдущем состоянии, полученном редуктором. Ожидается, что будет найдено одно из вместо этого известные имена свойств редуктора: "global", "language". Неожиданные свойства будут проигнорированы. Он по-прежнему работает и в основном жалуется, что в момент создания хранилища он не знает обо всех этих других контейнерах. способ обойти это предупреждение?
Зиф
@Zief трудно сказать. Сообщение "кажется" довольно ясным, редукторы ожидают свойств, которые не указаны. Это может быть связано с предоставлением значений по умолчанию для сериализованного состояния?
Лев,
Очень простое решение. Спасибо.
Ishara
1
@Joezhou хотел бы услышать, почему вы предпочитаете этот подход. Лично мне кажется, что это как раз то, для чего было предназначено промежуточное ПО.
michaelgmcd 04
1

Это основано на ответе Лео (который должен быть принятым ответом, поскольку он достигает цели вопроса без использования каких-либо сторонних библиотек).

Я создал класс Singleton, который создает хранилище Redux, сохраняет его с использованием локального хранилища и обеспечивает простой доступ к его хранилищу через геттер .

Чтобы использовать его, просто поместите следующий элемент Redux-Provider вокруг основного класса:

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

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

и добавьте в свой проект следующий класс:

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
источник