Как вы уже, наверное, знаете, браузерные хранилища данных, такие как localStorage и sessionStorage, сильно ограничены в своих размерах и для хранения большого количества данных не подходят. В разных браузерах этот размер варьируется, но в среднем принято считать, что это около 5 МБ.
Что же делать, если нужно больше ? Для этой ситуации в браузере предусмотрен другой Web API - IndexedDB. Его размеры и регулирование также сильно варьируются от браузера к браузеру, но обычно это около 50% доступного дискового пространства на устройстве пользователя.
Сам этот API достаточно неудобен в использовании, так как предполагает работу с большим количеством объёмных конструкций в коде и без использования заранее подготовленных собственных утилит не представляется возможным.
К счастью, для нас существует большое количество библиотек, которые упрощают взаимодействие с этим хранилищем в разы. Вот некоторые из них:
Dexie.js
idb
localForage
Нас больше интересует последний вариант - localForage. Он в отличие от остальных библиотек реализует API очень близкий по реализации localStorage, что ещё больше упрощает работу для решения большинства задач.
Перед тем как мы перейдём к его подробному разбору, хотелось бы дополнительно отметить несколько дополнительных преимуществ перед стандартным localStorage, помимо увеличенного размера хранилища:
Асинхронный API - при частых операциях с данными наш поток не будет забиваться как в случае с localStorage, все задачи выполняются отложено.
Формат хранения данных - localForage поддерживает любой формат данных (объекты, массивы, бинарные данные и так далее) и не требует предварительной сериализации / десериализации данных, так как под капотом работает IndexedDB, который умеет это делать из коробки. Это удобно и не требует дополнительных вычислительных трат на преобразование.
Итак, приступим к разбору.
Установка
# Eсли у вас npm
npm install localforage
# Если у вас yarn
yarn add localforage
Также можно создать глобальную переменную при помощи ручной установки тега script в коде:
<script src="localforage/dist/localforage.js"></script>
<script>console.log(localforage)</script>
Основные методы
Библиотека предлагает 2 варианта обработки операций: при помощи колбэков и при помощи промисов. Далее на примере метода getItem можно будет увидеть их оба.
getItem
getItem(key, callback)
Получает содержимое хранилища по ключу key и передаёт его в callback.
Если ранее был сохранён undefined по определённому ключу, то при получении этого элемента при помощи getItem() будет возвращён null. Это сделано по причинам совместимости localForage и ограничений localStorage.
localforage.getItem('somekey').then((value) => {
console.log(value);
});
localforage.getItem('somekey', (error, value) => {
console.log(value);
});
setItem
setItem(key, value, callback)
Сохраняет данные value по заданному ключу key и вызывает callback.
localforage.setItem('somekey', 'some value').then((value) => {
console.log(value);
});
removeItem
removeItem(key, callback)
Удаляет данные по ключу key и вызывает callback.
localforage.removeItem('somekey').then(() => {
console.log('Ключ somekey был успешно удалён.');
});
clear
clear(callback)
Удаляет все ключи из базы данных.
localforage.clear().then(() => {
console.log('База данных была очищена.');
});
length
length(callback)
Возвращает количество записей в хранилище и вызывает callback.
localforage.length().then((numberOfKeys) => {
console.log(numberOfKeys);
});
key
key(keyIndex, callback)
Получает имя ключа по его индексу (порядковому номеру) и передаёт его в callback.
localforage.key(2).then((keyName) => {
console.log(keyName);
});
keys
keys(callback)
Возвращает список всех ключей хранилища и передаёт их в callback.
localforage.keys().then((keys) => {
console.log(keys);
});
iterate
iterate(iteratorCallback, callback)
Проходит по всем элементам в хранилище используя функцию iteratorCallback и возвращает результат в callback.
localforage.iterate((value, key, iterationNumber) => {
console.log([key, value]);
});
Драйверы
Под драйверами в библиотеке подразумеваются типы хранилищ, используемых для записи данных. По-умолчанию их 3: IndexedDB, WebSQL (устарел и не рекомендуется к использованию) и localStorage. Если явно не указать необходимый драйвер, то localForage автоматически его выберет на основании поддержки браузером того или иного API в следующем порядке:
IndexedDB
WebSQL
localStorage
Мы можем управлять этим поведением при помощи метода setDriver.
setDriver(driverName)
setDriver([driverName, nextDriverName])
// Устанавливаем один предпочтительный драйвер
localforage.setDriver(localforage.LOCALSTORAGE);
// Устанавливаем несколько драйверов на случай, если один или несколько из них не будут поддерживаться в браузере
localforage.setDriver([localforage.WEBSQL, localforage.INDEXEDDB]);
Также при помощи метода defineDriver мы можем объявлять свои собственные драйверы.
Конфигурация
Каждому хранилищу, создаваемому при помощи localForage можно задавать определённые настройки при помощи конфигурации.
localforage.config({
driver: localforage.INDEXEDDB, // Указывает, какой драйвер использовать. Может быть: localforage.LOCALSTORAGE, localforage.INDEXEDDB или localforage.WEBSQL
name: 'myApp', // Имя базы данных (используется для идентификации хранилища)
version: 1.0, // Версия базы данных
description: 'My App storage', // Описание базы данных
size: 4980736, // Размер хранилища в байтах для WebSQL (по умолчанию — 5MB)
});
Несколько хранилищ
Для работы сразу с несколькими хранилищами сразу мы можем использовать методы:
createInstance - для создания нового инстанса
dropInstance - для удаления существующего
// Создаем первый инстанс с уникальным именем 'userData'
const userDataStore = localforage.createInstance({
name: 'myApp',
storeName: 'userData',
driver: localforage.INDEXEDDB,
});
// Записываем в него какие-то данные
userDataStore.setItem('username', 'Вася').then(() => {
console.log('Данные пользователя сохранены');
});
// Создаем второй инстанс с уникальным именем 'appSettings'
const appSettingsStore = localforage.createInstance({
name: 'myApp',
storeName: 'appSettings',
driver: localforage.LOCALSTORAGE,
});
// Также записываем в него какие-то данные
appSettingsStore.setItem('theme', 'dark').then(() => {
console.log('Настройки приложения сохранены.');
});
// Удаляем их оба
userDataStore.dropInstance().then(() => {
console.log('userDataStore был удалён')
});
appSettingsStore.dropInstance().then(() => {
console.log('appSettingsStore был удалён')
});
Дополнительно
Думаю на данном этапе у вас уже сложилась картина как устроена библиотека. Методы, которые я не перечислил здесь, вы можете самостоятельно найти в официальной документации.
Комментарии (10)
Viktor9354 Автор
20.01.2025 13:14Спасибо за комментарий !
Для сохранения больших черновиков отредактированных данных подходит, особенно когда работаешь с Data Science проектами или редакторами текста, кода и пр. Но это из личного опыта, а так вообще применений много. Знаю что такие штуки используются в играх, онлайн-картах и других приложениях, в которых необходимо хранить большое количество данных локально, чтобы каждый раз не ходить за ними на сервер или потому что на сервере их хранить необязательно.
Nn111
Не подумайте что придираюсь, но можете пожалуйста дать хотя бы один пример, где это надо? Только из вашей реальной работы, а не выдуманный?
Просто расширения для браузеров я довольно давно пишу и ни разу не упирался в потолок localstorage.
euhoo
В local-storage хранили словарики. В какой-то момент стало не хватать, перешли на ng-forage - обертка local-forage для ангуляра.
офтоп. У нас проект на ангуляре. В какой-то момент ng-forage перестал поддерживать поднятие версий ангуляра. На нативную indexedDb переходить не хотелось, прослойку в виде local-forage тоже особо не хотелось. В итоге скопипастил из local-forage несколько методов вместе с реализацией и этого хватило.
Viktor9354 Автор
Интересно узнать, почему решили словарики перенести в браузерные хранилища ?
euhoo
так кончилось место в local-storage
Viktor9354 Автор
Да, понял, непонятно почему бы их не хранить просто ввиде большого объекта в файле ? Или ввиде большого количества подобных объектов, разбросанных по проекту.
euhoo
Словарики с бэка приходят и периодически обновляются.
Можно было бы хранить в памяти где-нибудь в redux(ngrx), но если кратко - не хочется терять кэш словарей при перезагрузке страницы. Поэтому indexedDB, а не redux(ngrx в нашем случае ангуляра)
NetInstant1100
Если посмотреть разные вариации i18n, то довольно часто они сами все это реализовывают и не совсем понятно зачем что-то класть в redux.
Файлы прекрасно кешируются браузером, и если у вас переводы не обновляются по несколько раз в час - это не должно быть проблемой.
Viktor9354 Автор
Хороший вариант, согласен
starfair
Пожалуйста: есть сейчас такой офисный пакет, импортозамещаемый, называемый Р7. ПОд капотом у него бинарное ядро обработки самого документа, и фронтендом в качестве GUI - Chromium. Сразу понятно, что раз это по факту браузер, то о работе с внешними хранилищами данных, как это можно было делать через ADO или ODBC, напрямую работать не получится. А такое решение, вполне себе позволяет прикрутить хоть как то похожее на локальную СУБД, с помощью плагинов, в состав Р7. И других вариантов, без превлечения
санитаров, самописных серверов для работы с данными, там фактически и нет.