Использование localStorage с React Hooks
Сегодня мы узнаем, как хранить данные с помощью localStorage
, а также о некоторых способах их смешивания в вашем приложении React. Итак, когда нам нужно использовать локальное хранилище? Короткий и простой ответ касается сохранения данных, но это не означает, что все данные вашего приложения должны сохраняться. Нам нужны некоторые критерии для хранения порций информации.
localStorage
LocalStorage
— это доступное только для чтения свойство объекта window
для хранения данных между сеансами браузера (другими словами, хранилище браузера). API localStorage
совместим со многими современными браузерами, является одним из двух способов локального хранения данных (на стороне клиента), а максимальный объем информации, которую хранит localStorage
, составляет 5 МБ. Другой — sessionStorage
. Он сохраняет данные только во время активности сеанса. В противном случае localStorage
не имеет срока действия, даже если браузер закрыт или ОС перезагружается. В этом волшебная сила localStorage
, и об этом мы сегодня поговорим.
Другим способом хранения некоторых частей информации, о которых стоит упомянуть, являются файлы cookie. Он работает иначе, хранит меньше данных и может привести к множеству уязвимостей в вашем приложении, если вы используете его без правил безопасности (для этого у нас есть Web Security Academy, чтобы узнать все об этом).
Наиболее распространенными способами использования localStorage
разработчиками внешнего интерфейса являются:
- Функция темного режима.
- Вводимые пользователем формы (те, которые не содержат критической информации).
- Кэширование полученных данных из какого-либо API (не рекомендуется, если архитектура определена не очень хорошо, есть лучшие подходы).
- Сохраняющиеся данные в целом.
Стиль Vanilla JavaScript
Как мы упоминали ранее, localStorage
- это API, который позволяет вам получить доступ к объекту хранения документа. Он сохраняет пары ключ-значение, когда допустимый уникальный тип значения находится в строковом формате UTF-16 (позже мы увидим это на наглядном примере).
localStorage
позволяет устанавливать, стирать и извлекать данные. Если вам это нужно, это официальная документация.
Установка данных в localStorage
Чтобы установить некоторые данные (постоянные данные) в localStorage
, у нас есть два способа:
// Set key-value (both are equivalent, the second could be use to set
// keys programmatically), allows you to save data
localStorage.counter = 100;
// or
localStorage.setItem('counter', 100);
Попробуйте поставить номер. Даже если это объект, он будет преобразован в строку и будет рассматриваться как таковой. Чтобы увидеть этот случай, у нас есть пример:
// ❌ Set key-value (Wrong way, json object)
localStorage.user = {
name: 'Jonas',
age: 12,
description: 'a lot of confusing references'
}
// The result will be "[object Object]" because set method infers the
// .toString() value, so try it
console.log(localStorage.user);
Чтобы правильно установить объекты, см. следующий пример:
// If you need to save a JSON object (encode before and decode after)
localStorage.setItem('user', JSON.stringify({
name: 'Jonas',
age: 12,
description: 'a lot of confusing references'
}))
const user = JSON.parse(localStorage.getItem('user'))
// the output will be a JSON string
console.log(user);
Получение данных из localStorage
Для получения данных у нас тоже есть два способа.
// Get key-value (both are equivalent, the second could be use to get
// keys programmatically), allows you to get the saved data
let counter = localStorage.counter;
// or
counter = localStorage.getItem('counter');
console.log(counter);
помните, что getItem
возвращает только строковое значение.
Удаление данных, хранящихся в localStorage
Если срок действия localStorage
не истек, единственный способ удалить его данные - использовать два метода: removeItem
и clear
, смотрите это на примере:
// Remove key-value, allows you to remove data
localStorage.removeItem('counter');
// Alternativelly 'delete' works but it happens thanks
// to the language and not of the localStorage implementation
// so be careful, we don't recommend its use
delete localStorage.counter;
// Cleaning all data from localStorage
localStorage.clear();
removeItem()
и clear()
разные. removeItem()
удаляет пару ключ-значение, а clear()
удаляет все данные внутри localStorage
.
В следующем разделе мы рассмотрим пошаговое использование localStorage
в приложении React.
Стиль React hooks
Итак, у нас есть представление о том, как взаимодействовать с localStorage
. Теперь мы будем использовать его в качестве стиля React Hook. Следующие примеры кода доступны в репозитории react-localstorage-example.
Пример приложения react, которое мы создали для вас, представляет собой приложение-счетчик, в котором у вас есть четыре шага от простой до безопасной или зашифрованной реализации, мы обсудим все ниже, а пользовательский интерфейс выглядит следующим образом:
Использование useState hook
Первый пример работает только с хуком useState
и отображает кнопку с состоянием счетчика внутри. При каждом нажатии на кнопку счетчик увеличивается один за другим. Давайте посмотрим в следующем коде.
import { useState } from "react"
export default function SimpleMode(){
const [count, setCount] = useState(0)
return (
<div className="card">
<h3>Simple Example</h3>
<p>Click the button many times and try to refresh the page</p>
<br />
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
</div>
)
}
Попробуйте обновить страницу, и состояние счетчика потеряется, потому что useState
сохраняет состояние только некоторое время (пока не обновится браузер).
Добавление постоянства
Что ж, пришло время добавить немного настойчивости в этот пример. Мы представляем API localStorage
для хранения состояния счетчика в виде счетчика (ключ localStorage
). Каждый раз, когда вы обновляете страницу, useState
заряжает состояние значения из localStorage
или значение по умолчанию. Готово, у нас есть настойчивость, и данные сохраняются!
import { useEffect, useState } from "react"
export default function MediumMode(){
const [count, setCount] = useState(
Number(localStorage.getItem('counter')) || 0
)
const handleCounter = () => {
setCount(count + 1)
}
useEffect(()=>{
localStorage.setItem('counter', count)
}, [count])
return (
<div className="card">
<h3>Medium Example</h3>
<p>Click the button many times and try to refresh the page</p>
<br />
<button onClick={handleCounter}>
count is {count}
</button>
</div>
)
}
Теперь попробуйте обновить страницу, и состояние счетчика останется прежним. Но представьте, если вы должны сделать то же самое в нескольких компонентах, это ужасно! Но не волнуйтесь. У нас есть решение — React Hooks.
Реализация hook localStorage
React Hooks позволяет повторно использовать код и расширяет функциональность localStorage
на другие компоненты. Давайте посмотрим на реализацию после того, как нам понадобится выделенная папка для абстрагирования хука, что-то вроде этого.
Чтобы создать собственный hook, нам нужна только функция, которая возвращает состояние, и его функция set
, как мы видим в следующем коде (localstorage.js
, наш пользовательский hook).
import { useEffect, useState } from "react"
const decode = (value) => {
return JSON.stringify(value)
}
const encode = (value) => {
return JSON.parse(value)
}
// useLocalStorage hook
const useLocalStorage = (key, defaultState) => {
const [value, setValue] = useState(
encode(localStorage.getItem(key)||null) || defaultState
)
useEffect(() => {
localStorage.setItem(key, decode(value))
}, [value])
return [value, setValue]
}
export {
useLocalStorage
}
Теперь каждому компоненту нужно использовать только уникальный ключ имени, чтобы избежать конфликтов с парами ключ-значение других компонентов и состоянием по умолчанию в качестве второго параметра. Это простая и полезная реализация, но если вам нужно что-то зрелое и законченное, мы рекомендуем найти библиотеки с открытым исходным кодом, такие как use-local-storage-state.
Итак, у нас есть новый пользовательский React hook под названием useLocalStorage
, который заменит предыдущую реализацию.
import { useLocalStorage } from "../hooks/localstorage"
export default function HookMode(){
const [counter, setCounter] = useLocalStorage('counter', 0);
const [secondCounter, setSecondCounter] = useLocalStorage('secondCounter', 0);
return (
<div className="card">
<h3>Hook Example</h3>
<p>Click the button many times and try to refresh the page</p>
<br />
<button onClick={() => setCounter(counter + 1)}>
Button 1: count is {counter}
</button>
<br /><br />
<button onClick={() => setSecondCounter(secondCounter + 1)}>
Button 2: count is {secondCounter}
</button>
</div>
)
}
Объем кода был значительно уменьшен, и мы можем повторно использовать hook (много раз, сколько нам нужно).
Разное использование
Чтобы закончить этот раздел реализации. У нас есть различное использование localStorage
. Обычно данные из localStorage
доступны, и каждый пользователь может видеть их в виде простого текста (поскольку они основаны на строках), но если нам нужно скрыть их от пользователей, есть способ зашифровать их. Нам нужно использовать библиотеку encrypt-storage.
Прежде нам нужно настроить это зашифрованное хранилище, для этого создайте новый файл .js
следующим образом:
import { EncryptStorage } from 'encrypt-storage'
const encryptedLocalStorage = new EncryptStorage(
'SET_YOUR_SECRET_KEY',
{
// Keys used by this library will have this prefix
// e.g.: 'enc' + ':' + 'input-data' = 'enc:input-data' as key name
prefix:'enc',
// Encryption algorithm type
encAlgorithm: 'AES',
// Storage type (localStorage and sessionStorage are supported)
storageType: 'localStorage'
}
);
export {
encryptedLocalStorage
}
encryptedLocalStorag
по существу аналогичен localStorage
, но имеет дополнительные реализации для шифрования хранилища и использования в качестве объекта localStorage
.
Мы уже знаем, как создать пользовательский React hook с помощью localStorage
. Нам нужно создать еще одно подобное, используя объект хранилища encryptedLocalStorage
. Обновим файл localstorage.js
(создан ранее).
import { useEffect, useState } from "react"
import { encryptedLocalStorage } from "../utils/secureLocalStorage"
const decode = (value) => {
return JSON.stringify(value)
}
const encode = (value) => {
return JSON.parse(value)
}
// useLocalStorage hook
const useLocalStorage = (key, defaultState) => {
const [value, setValue] = useState(
encode(localStorage.getItem(key)||null) || defaultState
)
useEffect(() => {
localStorage.setItem(key, decode(value))
}, [value])
return [value, setValue]
}
// New encrypted localStorage
const useSecureLocalStorage = (key, defaultState) => {
const [value, setValue] = useState(
encode(encryptedLocalStorage.getItem(key)||null) || defaultState
)
useEffect(() => {
encryptedLocalStorage.setItem(key, decode(value))
}, [value])
return [value, setValue]
}
export {
useLocalStorage,
useSecureLocalStorage
}
Итак, пришло время использовать hook useSecureLocalStorage
.
import { useSecureLocalStorage } from "../hooks/localstorage";
export default function SecureHookMode(){
const [counter, setCounter] = useSecureLocalStorage('counter', 0);
const [secondCounter, setSecondCounter] = useSecureLocalStorage('secondCounter', 0);
return (
<div className="card">
<h3>Secure Hook Example</h3>
<p>Click the button many times and try to refresh the page</p>
<br />
<button onClick={() => setCounter(counter + 1)}>
Button 1: encrypted count is {counter}
</button>
<br /><br />
<button onClick={() => setSecondCounter(secondCounter + 1)}>
Button 2: encrypted count is {secondCounter}
</button>
</div>
)
}
Используйте инструменты разработчика вашего браузера (в данном случае Brave), перейдите в Приложение (Application) > Инструмент локального хранилища (Local Storage) и проверьте файл localStorage
. Сохраненные данные в настоящее время зашифрованы и имеют выбранный нами префикс (в данном случае enc
).
Используйте его только в исключительных случаях. Даже если данные зашифрованы, ключ находится внутри файлов пакета .js
в виде обычного текста (генерируется во время сборки). Таким образом, это небезопасно и работает только для сокрытия данных от пользователя.
В качестве бонуса перейдите на эту страницу https://vuejs.org/ и проверьте ее localStorage
. Вы видите то же самое? Попробуйте изменить тему этой страницы с темой кнопки и посмотрите, как изменится сохраненное значение с ключом vitepress-theme-appearance
.
Использование localStorage с умом
Мы видели, как использовать localStorage с компонентами React, React hooks и vanilla js, но его использование вызывает споры. Мы нашли эту статью от Рэндалла Деггеса, у которого есть профиль исследователя безопасности, в котором он в основном говорит: «Пожалуйста, прекратите использовать Local Storage» — фраза и аргументы, которые заставили нас много думать.
Дизайн и функциональность localStorage
настолько просты, и он доступен для JavaScript, что опасно, если мы думаем об атаках, связанных с выполнением кода, таких как XSS. Если ваше приложение React содержит конфиденциальную и личную информацию в localStorage
, она будет отправлена на вредоносные серверы. Многие разработчики внешнего интерфейса используют localStorage
для хранения JWT (веб-токена JSON), но они не понимают значения JWT (эквивалент имени пользователя и пароля), доступного через JavaScript. Критическая и конфиденциальная информация всегда будет обрабатываться на стороне сервера.
Для этого мы не рекомендуем использовать localStorage, если:
- Храните конфиденциальные данные внутри.
- Необходимый вам объем хранилища превышает 5 МБ.
Если вы начинаете с безопасного мышления, мы рекомендуем вам ознакомиться с рекомендациями по безопасности React, которые, возможно, необходимо знать разработчику React.