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
, и сделано это для:
- Отслеживания изменений и обновления
LocalStorage
. - Получения сохраненного значения из
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.
Какой бы вариант вы не выбрали, важно также сохранять контроль над изменениями состояния. Ведь после того, как состояние будет установлено, у некоторых пользователей приложение не сможет продолжать работу, если в более новой версии вы измените код, связанный с этим состоянием.
Спасибо за внимание. 🤔
Читайте также:
- Создаем приложение React с нуля в 2021 году
- Обзор техник кэширования в React
- 3 способа улучшить управление состоянием в React
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Dilantha Prasanjith: 5 Methods to Persisting State Between Page Reloads in React