Руководство по различным типам хранилищ в браузере

От автора: в этой статье мы рассмотрим различные возможности хранения данных в браузере. Мы рассмотрим три варианта использования каждого метода, чтобы понять плюсы и минусы. В конце концов, вы сможете решить, какое хранилище лучше всего подходит для вашего варианта использования. Итак, начнем!

В серверной разработке хранилище — это обычная часть работы. Данные приложений хранятся в базах данных, файлы — в хранилищах объектов, временные данные — в кэшах… есть, по-видимому, бесконечные возможности для хранения любых данных. Но хранение данных не ограничивается только серверной частью. Фронт-энд часть (браузер) также оснащена множеством вариантов для хранения данных. Используя это хранилище, мы можем повысить производительность приложения, сохранить пользовательские настройки, сохранить состояние приложения в течение нескольких сеансов или даже на разных компьютерах.

localStorageAPI

localStorage является одним из самых популярных вариантов хранения в браузере и часто используется многими разработчиками. Данные хранятся между сеансами, никогда не передаются серверу и доступны для всех страниц в рамках одного протокола и домена. Хранилище ограничено ~ 5 МБ.

Удивительно, но команда Google Chrome не рекомендует использовать эту опцию, поскольку она блокирует основной поток и недоступна для web workers и service workers. Они запустили эксперимент, KV Storage, в качестве лучшего варианта, но это была всего лишь пробная версия, которая, похоже, еще ни к чему не пришла.

localStorage API доступен как window.localStorage и может сохранить только UTF-16 строки. Мы должны убедиться, что преобразовали данные в строки, прежде чем сохранять их в localStorage. Основные три функции:

setItem(‘key’, ‘value’)

getItem(‘key’)

removeItem(‘key’)

Все они синхронны, что упрощает работу, но блокируют основной поток. Стоит отметить, что у localStorage есть близнец sessionStorage. Единственная разница в том, что данные, хранящиеся в sessionStorage, будут сберегаться только в текущем сеансе, но API остается прежним.

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

Вы можете установить чек-бокс и обновить страницу, чтобы увидеть, что состояние сохраняется между сеансами. Посмотрите на функции save и load, чтобы увидеть, как преобразовать значение в строку, и как я парсировать его. Важно помнить, что мы можем хранить только строки.

В этом втором примере загружаются имена покемонов из PokéAPI.

Мы отправляем запрос GET, используя fetch и перечисляя все имена в элементе ul. Получив ответ, мы кэшируем его в localStorage, чтобы следующее посещение могло быть намного быстрее или даже можно было работать в автономном режиме. Мы должны использовать JSON.stringify для преобразования данных в строку и JSON.parse для чтения ее из кэша.

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

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

Мы также будем использовать эти три примера в следующих вариантах хранения. Я раздвоил демонстрацию и просто изменил соответствующие функции. Общий скелет одинаков для всех методов.

API IndexedDB

IndexedDB — это современное решение для хранения данных в браузере. Он может хранить значительный объем структурированных данных — даже файлы и большие двоичные объекты. Как и любая база данных, IndexedDB индексирует данные для эффективного выполнения запросов. Использовать IndexedDB сложнее. Нам нужно создать базу данных, таблицы и использовать транзакции.

По сравнению с localStorage IndexedDB требуется намного больше кода. В примерах я использую собственный API с оболочкой Promise, но я настоятельно рекомендую использовать сторонние библиотеки. Я советую localForage, потому что он использует тот же localStorageAPI, но реализует его с постепенным расширением, то есть, если ваш браузер поддерживает IndexedDB, он будет использовать его; а если нет, он вернется к localStorage.

Давайте перейдем к нашему примеру пользовательских настроек!

idb — это оболочка Promise, которую мы используем вместо работы с низкоуровневым API на основе событий. Они почти идентичны, так что не волнуйтесь. Первое, что следует заметить, это то, что каждый доступ к базе данных является асинхронным, то есть мы не блокируем основной поток. По сравнению с localStorage, это большое преимущество.

Нам нужно открыть соединение с базой данных, чтобы она была доступна во всем приложении для чтения и записи. Мы задаем базе данных имя my-db, версию схемы 1, и функцию обновления для применения изменений между версиями. Это очень похоже на миграцию базы данных. Наша схема базы данных проста: только один объект хранилища, preferences. Хранилище объектов — это эквивалент таблицы SQL. Чтобы записывать или читать из базы данных, мы должны использовать транзакции. Это утомительная часть использования IndexedDB. Посмотрите на новые функции save и load в демонстрации.

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

Данные приложений, такие как в нашем примере с Pokémon, являются сильной стороной IndexedDB. В этой базе данных можно хранить сотни мегабайт и даже больше. Вы можете хранить всех покемонов в IndexedDB и делать их доступными офлайн и даже индексировать! Это определенно то, что нужно выбрать для хранения данных приложения.

Я пропустил реализацию третьего примера, так как IndexedDB в этом случае не отличается по сравнению с localStorage. Даже с IndexedDB пользователь по-прежнему не сможет делиться выбранной страницей с другими или добавлять ее в закладки для будущего использования. Оба метода не подходят для этого варианта использования.

Файлы куки

Использование файлов куки — это уникальный способ хранения. Это единственное хранилище, которое также используется совместно с сервером. Файлы куки отправляются как часть каждого запроса. Это может быть, когда пользователь просматривает страницы в приложении или когда пользователь отправляет запросы Ajax. Это позволяет нам создавать общее состояние между клиентом и сервером, а также разделять состояние между несколькими приложениями в разных поддоменах. Это невозможно для других вариантов хранения, описанных в этой статье. Одно предостережение: файлы куки отправляются с каждым запросом, что означает, что мы должны сохранять файлы куки небольшими, чтобы поддерживать приемлемый размер запроса.

Наиболее распространенное использование файлов куки — это аутентификация, что выходит за рамки данной статьи. Как и localStorage, файлы куки, могут хранить только строки. Файлы куки объединяются в одну строку, разделенную точкой с запятой, и отправляются в заголовке запроса файла куки. Вы можете установить множество атрибутов для каждого файла куки, например, срок действия, разрешенные домены, разрешенные страницы и многое другое.

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

Сохранение предпочтений пользователя в файле куки может оказаться полезным, если сервер может каким-то образом его использовать. Например, в случае использования темы сервер может доставить соответствующий файл CSS и уменьшить потенциальный размер пакета (в случае, если мы выполняем рендеринг на стороне сервера). Другой вариант использования может заключаться в том, чтобы поделиться этими предпочтениями между несколькими приложениями в поддоменах без базы данных.

Чтение и запись файлов куки с помощью JavaScript выполнить не так просто, как вы думаете. Чтобы сохранить новый файл куки, вам необходимо установить document.cookie — посмотрите функцию save в приведенном выше примере. Я устанавливаю файл куки dark_theme и добавляю ему атрибут max-age, чтобы гарантировать, что срок его действия не истечет при закрытии вкладки. Кроме того, я добавляю атрибуты SameSite и Secure. Это необходимо, потому что CodePen использует iframe для запуска примеров, но в большинстве случаев они вам не понадобятся. Для чтения файла куки необходимо проанализировать строку cookie.

Строка cookie выглядит так:

key1=value1;key2=value2;key3=value3

Итак, сначала нам нужно разделить строку точкой с запятой. Теперь у нас есть массив файлов куки в форме key1=value1, поэтому нам нужно найти правильный элемент в массиве. В итоге мы разделяем их по знаку равенства и получаем последний элемент в новом массиве. Немного утомительно, но когда вы реализуете функцию getCookie (или скопируете ее из моего примера: P), вы можете забыть об этом.

Сохранение данных приложения в cookie может быть плохой идеей! Это резко увеличит размер запроса и снизит производительность приложения. Кроме того, сервер не может извлечь выгоду из этой информации, поскольку это устаревшая версия информации, уже имеющейся в его базе данных. Если вы используете файлы куки, убедитесь, что они маленькие.

Пример разбивки на страницы также не подходит для файлов куки, как localStorage и IndexedDB. Текущая страница — это временное состояние, которым мы хотели бы поделиться с другими, и ни один из этих методов не обеспечивает его.

Хранилище URL

URL-адрес сам по себе не является хранилищем, но это отличный способ создать общее состояние. На практике это означает добавление параметров запроса к текущему URL-адресу, которые можно использовать для воссоздания текущего состояния. Лучшим примером могут служить поисковые запросы и фильтры. Если мы выполним поиск по запросу flexbox на CSS-Tricks, URL-адрес будет обновлен на https://css-tricks.com/?s=flexbox. Видите, как легко поделиться поисковым запросом, если мы используем URL-адрес? Еще одно преимущество состоит в том, что вы можете просто нажать кнопку обновления, чтобы получить новые результаты вашего запроса или даже добавить его в закладки.

Мы можем сохранять в URL-адресе только строки, а его максимальная длина ограничена, поэтому у нас не так много места. Придется сохранять маленькое состояние. Никто не любит длинные и пугающие URL-адреса.

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

Мы можем получить доступ к строке запроса window.location.search и, к счастью, ее можно проанализировать с помощью класса URLSearchParams. Больше не нужно применять сложный синтаксический анализ строк. Когда мы хотим прочитать текущее значение, мы можем использовать функцию get. Когда мы хотим записать запрос, мы можем использовать set. Недостаточно просто установить значение; нам также необходимо обновить URL-адрес. Это можно сделать с помощью history.pushState или history.replaceState, в зависимости от поведения, которое вам нужно.

Я бы не рекомендовал сохранять предпочтения пользователя в URL-адресе, поскольку нам придется добавлять это состояние к каждому URL-адресу, который посещает пользователь, и мы не можем этого гарантировать; например, если пользователь нажимает ссылку в поиске Google.

Как и в случае куки, мы не можем сохранять данные приложения в URL-адресе, поскольку у нас мало места. И даже если нам удастся их сохранить, URL-адрес будет длинным и не будет привлекательным для клика. Это может выглядеть как своего рода фишинговая атака.

Как и в нашем примере с разбивкой на страницы, временное состояние приложения лучше всего подходит для строки запроса URL. Опять же, вы не можете видеть изменения URL-адреса, но URL-адрес обновляется с помощью параметром запроса ?page=x каждый раз, когда вы нажимаете на страницу. Когда веб-страница загружается, она ищет этот параметр запроса и соответственно выбирает нужную страницу. Теперь мы можем поделиться этим URL-адресом с нашими друзьями, и они смогут насладиться нашим любимым покемоном.

Cache API

Cache API — это хранилище для сетевого уровня. Оно используется для кэширования сетевых запросов и ответов на них. Cache API идеально подходит для service workers. Они могут перехватить каждый сетевой запрос, а с помощью Cache API они могут легко кэшировать оба запроса. service worker также может вернуть существующий элемент кеша в качестве сетевого ответа, вместо того, чтобы получать его с сервера. Таким образом можно сократить время загрузки сети и заставить приложение работать даже в автономном режиме. Первоначально он был создан для service worker, но в современных браузерах Cache API также доступен в контекстах окна, iframe и worker. Это очень мощный API, который может значительно улучшить пользовательский интерфейс приложения.

Как и IndexedDB, хранилище Cache API не ограничено, и вы можете хранить сотни мегабайт и даже больше, если вам нужно. API асинхронный, поэтому он не блокирует основной поток. И он доступен через глобальное свойство caches.

Чтобы узнать больше о Cache API, команда Google Chrome подготовила отличное руководство.

Открыть демонстрацию.

Бонус: расширение для браузера

Если вы создаете расширение для браузера, у вас есть другой вариант для хранения данных. Я обнаружил это, когда работал над своим расширением daily.dev. Он доступен через chrome.storage или browser.storage, если вы используете полифилл Mozilla. Не забудьте запросить разрешение на хранение в своем манифесте, чтобы получить доступ.

Существует два типа хранилища: локальное и синхронизируемое. Название локального хранилища говорит само за себя; это означает, что оно не используется совместно и хранится локально. Синхронизируемое хранилище синхронизируется как часть учетной записи Google, и где бы вы ни устанавливали расширение с той же учетной записью, это хранилище будет синхронизироваться. Довольно крутая функция, если вы спросите меня. У обоих одинаковый API, поэтому при необходимости очень легко переключаться между ними. Это асинхронное хранилище, поэтому оно не блокирует основной поток как, например, localStorage. К сожалению, я не могу создать демонстрацию для этого варианта хранилища, поскольку для этого требуется расширение для браузера, но оно довольно простое в использовании и почти такое же, как localStorage. Дополнительные сведения о точной реализации см. в документации Chrome.

Заключение

В браузере есть множество опций, которые мы можем использовать для хранения данных. Следуя совету команды Chrome, нашим хранилищем должно быть IndexedDB. Это асинхронное хранилище, в котором достаточно места для хранения всего, что мы захотим. localStorage не рекомендуется, но его проще использовать, чем IndexedDB. Файлы куки — отличный способ поделиться состоянием клиента с сервером, но в основном они используются для аутентификации.

Если вы хотите создать страницы с общим состоянием, например, страницу поиска, используйте строку запроса URL-адреса для хранения этой информации. Наконец, если вы создаете расширение, обязательно прочтите о chrome.storage.

Автор: Ido Shamun

Источник: css-tricks.com

Редакция: Команда webformyself.