5 Методов сохранения состояния в промежутках между перезагрузками страниц в React



Книга 5 Методов сохранения состояния в промежутках между перезагрузками страниц в React

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.


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


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


832   0  

Comments

    Ничего не найдено.