1. LocalStorage  —  классовые компоненты

Один из простых вариантов для сохранения состояния  —  использовать localStorage в браузере. Рассмотрим пример:

import React from "react";
import "./styles.css";
export default class App extends React.Component {
constructor() {
    super();
    this.state = {
      count: 0
    }
  }
increaseCount = () => {
    return this.setState({...this.state, count: this.state.count + 1});
  }
decreaseCount = () => {
    return this.setState({...this.state, count: this.state.count - 1});
  }
render() {
return (
      <div className="App">
        <h1> Count {this.state.count} </h1>
        <button onClick={this.increaseCount}>+</button> 
        <button onClick={this.decreaseCount}>-</button>
      </div>
    );
}
}

В этом компоненте с состоянием имеется count. Теперь предположим, что нам надо при перезагрузке страницы сохранить значение этого счетчика count. Для этого просто задействуем localStorage:

import React from "react";
import "./styles.css";
export default class App extends React.Component {
constructor() {
    super();
    this.state = JSON.parse(window.localStorage.getItem('state')) || {
      count: 0
    }
  }
setState(state) {
    window.localStorage.setItem('state', JSON.stringify(state));
    super.setState(state);
  }
increaseCount = () => {
    return this.setState({...this.state, count: this.state.count + 1});
  }
decreaseCount = () => {
    return this.setState({...this.state, count: this.state.count - 1});
  }
render() {
return (
      <div className="App">
        <h1> Count {this.state.count} </h1>
        <button onClick={this.increaseCount}>+</button> 
        <button onClick={this.decreaseCount}>-</button>
      </div>
    );
}
}

Теперь при задействовании localStorage в компоненте с состоянием значение этого состояния сохраняется, когда вызывается метод setState.

Это простой подход для сохранения состояния в классовых компонентах. Посмотрим, как добиться того же в функциональном компоненте.

2. LocalStorage  —  функциональные компоненты

Первым делом преобразуем компонент, основанный на классах, в функциональный компонент:

import React, { useEffect, useState } from "react";
import "./styles.css";

export default function App() {
  const [count, setCount] = useState(0);

  const increaseCount = () => {
    return setCount(count + 1);
  }
  const decreaseCount = () => {
    return setCount(count - 1)
  }

  return (
    <div className="App">
      <h1> Count {count} </h1>
      <button onClick={increaseCount}>+</button>
      <button onClick={decreaseCount}>-</button>
    </div>
  );
}

A теперь посмотрим, как при добавлении localStorage в функциональный компонент с состоянием добиться сохранения этого состояния:

import React, { useEffect, useState } from "react";
import "./styles.css";

export default function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setCount(JSON.parse(window.localStorage.getItem('count')));
  }, []);

  useEffect(() => {
    window.localStorage.setItem('count', count);
  }, [count]);

  const increaseCount = () => {
    return setCount(count + 1);
  }
  const decreaseCount = () => {
    return setCount(count - 1)
  }

  return (
    <div className="App">
      <h1> Count {count} </h1>
      <button onClick={increaseCount}>+</button>
      <button onClick={decreaseCount}>-</button>
    </div>
  );
}

В случае с функциональным компонентом применен несколько иной подход. Здесь использован хук useEffect, и сделано это для:

  1. Отслеживания изменений и обновления LocalStorage.
  2. Получения сохраненного значения из LocalStorage при инициализации.

3. LocalStorage с Redux Store

Но при сохранении состояния в localStorage на уровне компонента возникает одна проблема. И связана она с наличием нескольких экземпляров одного и того же компонента. Это приводит к неожиданному поведению, так как в localStorage создаются повторяющиеся ключи.

У этой проблемы два решения:

  • Передать идентификатор в повторно используемый компонент и задействовать его для хранения значения в localStorage.
  • Или сохранять состояние на более высоком уровне.

Для сохранения состояния приложения в localStorage будем использовать Redux.

Сначала посмотрим, как сделать это вручную, а затем попробуем задействовать библиотеку Redux Persist, которая поможет справиться с нашей задачей:

import { createStore, combineReducers } from 'redux';
import listReducer from '../features/list/reducer';

const saveToLocalStorage = (state) => {
  try {
    localStorage.setItem('state', JSON.stringify(state));
  } catch (e) {
    console.error(e);
  }
};

const loadFromLocalStorage = () => {
  try {
    const stateStr = localStorage.getItem('state');
    return stateStr ? JSON.parse(stateStr) : undefined;
  } catch (e) {
    console.error(e);
    return undefined;
  }
};

const rootReducer = combineReducers({
  list: listReducer
});

const persistedStore = loadFromLocalStorage();

const store = configureStore(rootReducer, persistedStore);

store.subscribe(() => {
  saveToLocalStorage(store.getState());
});

export default store;

Здесь при использовании localStorage с Redux мы подписываемся на обновления хранилища и сохраняем это в localStorage. А при инициализации приложения передаем исходное состояние из localStorage.

4. Redux Persist

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

Обратите внимание: здесь пригодится популярная библиотекаRedux Persist.

import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // установлен по умолчанию в localStorage для веба

import rootReducer from './reducers'

const persistConfig = {
  key: 'root',
  storage,
}

const persistedReducer = persistReducer(persistConfig, rootReducer)

export default () => {
  let store = createStore(persistedReducer)
  let persistor = persistStore(store)
  return { store, persistor }
}

А сохранять и инициализировать persistStore нам помогает persistReducer из Redux Persist.

5. Параметры URL

Наконец добрались до самого очевидного варианта  —  использования параметров URL для сохранения состояния. А этот подход годится, в случае если данные предельно простые и со значениями-примитивами. Это обусловлено ограничениями по длине URL.

import React, { useEffect, useState } from "react";
import "./styles.css";
import qs from "qs";
import { createBrowserHistory } from "history";

export default function App() {
  const [count, setCount] = useState(0);

  const history = createBrowserHistory();

  useEffect(() => {
    const filterParams = history.location.search.substr(1);
    const filtersFromParams = qs.parse(filterParams);
    if (filtersFromParams.count) {
      setCount(Number(filtersFromParams.count));
    }
  }, []);

  useEffect(() => {
    history.push(`?count=${count}`);
  }, [count]);

  const increaseCount = () => {
    return setCount(count + 1);
  }
  const decreaseCount = () => {
    return setCount(count - 1)
  }

  return (
    <div className="App">
      <h1> Count {count} </h1>
      <button onClick={increaseCount}>+</button>
      <button onClick={decreaseCount}>-</button>
    </div>
  );
}

Если приглядеться к этому коду, то мы заметим, что он добавляет состояние в историю браузера. Поэтому при инициализации компонента мы наследуем исходные значения параметров URL.

Одно из существенных преимуществ здесь  —  это возможность сохранения состояний для перезагрузки, а также перемещения между историческими состояниями с помощью кнопки браузера «Назад».

Заключение

Для сохранения состояния приложения на React используются localStorage и параметры URL.

В простой ситуации вполне сгодятся параметры URL. Если данные немного сложнее, стоит отдать предпочтение сохранению состояния в localStorage. В случае с localStorage решаем, где сохранять состояние: на уровне компонентов или на уровне приложений.

Чтобы упростить себе задачу и не заниматься сохранением и восстановлением состояния приложения вручную, задействуем библиотеки типа Redux Persist.

Какой бы вариант вы не выбрали, важно также сохранять контроль над изменениями состояния. Ведь после того, как состояние будет установлено, у некоторых пользователей приложение не сможет продолжать работу, если в более новой версии вы измените код, связанный с этим состоянием.

Спасибо за внимание. 🤔

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Dilantha Prasanjith: 5 Methods to Persisting State Between Page Reloads in React

Предыдущая статьяВсё, что должен знать разработчик ПО о качестве кода
Следующая статьяИИ: постижение законов сверхразума