Офлайн-данные

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

Хранилище

Хранилище — это не только файлы и активы, оно может включать в себя и другие типы данных. Во всех браузерах, поддерживающих PWA, для хранения на устройстве доступны следующие API:

  • IndexedDB : вариант объектного хранилища NoSQL для структурированных данных и больших двоичных объектов (двоичных данных).
  • WebStorage: способ хранения пар строк ключ/значение с использованием локального хранилища или хранилища сеансов. Он недоступен в контексте сервисного работника. Этот API является синхронным, поэтому его не рекомендуется использовать для хранения сложных данных.
  • Хранение кэша: Как описано в модуле «Кэширование» .

Вы можете управлять хранилищем всех устройств с помощью API Storage Manager на поддерживаемых платформах. API Cache Storage и IndexedDB обеспечивают асинхронный доступ к постоянному хранилищу для PWA, и доступ к ним возможен из основного потока, веб-воркеров и сервис-воркеров. Оба играют важную роль в обеспечении надежной работы PWA, когда сеть нестабильна или отсутствует. Но когда следует использовать каждый?

Используйте API Cache Storage для сетевых ресурсов, к которым вы можете получить доступ, запросив их через URL-адрес, например HTML, CSS, JavaScript, изображения, видео и аудио.

Используйте IndexedDB для хранения структурированных данных. Сюда входят данные, которые должны быть доступны для поиска или объединения в стиле NoSQL, а также другие данные, например данные, специфичные для пользователя, которые не обязательно соответствуют URL-запросу. Обратите внимание, что IndexedDB не предназначен для полнотекстового поиска.

ИндекседБД

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

В следующем примере создается база данных для хранения рецептов приготовления.

Создание и открытие базы данных

Чтобы открыть базу данных:

  1. Используйте функцию openDB для создания новой базы данных IndexedDB под названием cookbook . Поскольку базы данных IndexedDB имеют версии, вам необходимо увеличивать номер версии каждый раз, когда вы вносите изменения в структуру базы данных. Второй параметр — версия базы данных. В примере установлено значение 1.
  2. Объект инициализации, содержащий обратный вызов upgrade() передается в openDB() . Функция обратного вызова вызывается при первой установке базы данных или при ее обновлении до новой версии. Эта функция — единственное место, где могут происходить действия. Действия могут включать создание новых хранилищ объектов (структур, которые IndexedDB использует для организации данных) или индексов (по которым вы хотите выполнять поиск). Здесь также должна произойти миграция данных. Обычно функция upgrade() содержит оператор switch без операторов break , позволяющий выполнять каждый шаг по порядку, в зависимости от старой версии базы данных.
import { openDB } from 'idb';

async function createDB() {
  // Using https://github.com/jakearchibald/idb
  const db = await openDB('cookbook', 1, {
    upgrade(db, oldVersion, newVersion, transaction) {
      // Switch over the oldVersion, *without breaks*, to allow the database to be incrementally upgraded.
    switch(oldVersion) {
     case 0:
       // Placeholder to execute when database is created (oldVersion is 0)
     case 1:
       // Create a store of objects
       const store = db.createObjectStore('recipes', {
         // The `id` property of the object will be the key, and be incremented automatically
           autoIncrement: true,
           keyPath: 'id'
       });
       // Create an index called `name` based on the `type` property of objects in the store
       store.createIndex('type', 'type');
     }
   }
  });
}

В примере создается хранилище объектов внутри базы данных cookbook , называемое recipes , со свойством id , установленным в качестве индексного ключа хранилища, и создается еще один индекс с именем type на основе свойства type .

Давайте посмотрим на только что созданное хранилище объектов. После добавления рецептов в хранилище объектов и открытия DevTools в браузерах на базе Chromium или Web Inspector в Safari вы должны увидеть следующее:

Safari и Chrome показывают содержимое IndexedDB.

Добавление данных

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

  1. Запустите транзакцию с mode readwrite .
  2. Получите хранилище объектов, куда вы будете добавлять данные.
  3. Вызовите add() с данными, которые вы сохраняете. Метод получает данные в форме словаря (как пары ключ/значение) и добавляет их в хранилище объектов. Словарь должен быть клонирован с помощью структурированного клонирования . Если вы хотите обновить существующий объект, вместо этого вы должны вызвать метод put() .

Транзакции имеют обещание done , которое выполняется, когда транзакция завершается успешно, или отклоняется с ошибкой транзакции .

Как поясняется в документации библиотеки IDB , если вы записываете данные в базу данных, tx.done является сигналом того, что все было успешно зафиксировано в базе данных. Однако полезно дождаться отдельных операций, чтобы можно было увидеть любые ошибки, которые приводят к сбою транзакции.

// Using https://github.com/jakearchibald/idb
async function addData() {
  const cookies = {
      name: "Chocolate chips cookies",
      type: "dessert",
        cook_time_minutes: 25
  };
  const tx = await db.transaction('recipes', 'readwrite');
  const store = tx.objectStore('recipes');
  store.add(cookies);
  await tx.done;
}

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

Получение данных

Вот как вы получаете данные из IndexedDB:

  1. Запустите транзакцию и укажите хранилище или хранилища объектов и, при необходимости, тип транзакции.
  2. Вызовите objectStore() из этой транзакции. Обязательно укажите имя хранилища объектов.
  3. Вызовите get() с ключом, который вы хотите получить. По умолчанию хранилище использует свой ключ в качестве индекса.
// Using https://github.com/jakearchibald/idb
async function getData() {
  const tx = await db.transaction('recipes', 'readonly')
  const store = tx.objectStore('recipes');
// Because in our case the `id` is the key, we would
// have to know in advance the value of the id to
// retrieve the record
  const value = await store.get([id]);
}

Менеджер по хранению

Знание того, как управлять хранилищем вашего PWA, особенно важно для правильного хранения и потоковой передачи сетевых ответов.

Емкость хранилища распределяется между всеми вариантами хранилища, включая Cache Storage, IndexedDB, Web Storage и даже файл Service Worker и его зависимости. Однако объем доступного хранилища варьируется от браузера к браузеру. У вас вряд ли закончится; сайты могут хранить мегабайты и даже гигабайты данных в некоторых браузерах. Chrome, например, позволяет браузеру использовать до 80% всего дискового пространства, а отдельный источник может использовать до 60% всего дискового пространства. В браузерах, поддерживающих Storage API, вы можете узнать, сколько места еще доступно для вашего приложения, его квоту и его использование. В следующем примере API хранилища используется для получения оценки квоты и использования, а затем вычисляет процент использования и оставшиеся байты. Обратите внимание, что navigator.storage возвращает экземпляр StorageManager . Имеется отдельный интерфейс Storage и их легко запутать.

if (navigator.storage && navigator.storage.estimate) {
  const quota = await navigator.storage.estimate();
  // quota.usage -> Number of bytes used.
  // quota.quota -> Maximum number of bytes available.
  const percentageUsed = (quota.usage / quota.quota) * 100;
  console.log(`You've used ${percentageUsed}% of the available storage.`);
  const remaining = quota.quota - quota.usage;
  console.log(`You can write up to ${remaining} more bytes.`);
}

В Chromium DevTools вы можете увидеть квоту вашего сайта и объем используемого хранилища в разбивке по тому, что его использует, открыв раздел «Хранилище» на вкладке «Приложение» .

Инструменты разработчика Chrome в приложении, раздел «Очистить хранилище»

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

Сохранение данных

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

Чтобы запросить постоянное хранилище, вызовите StorageManager.persist() . Как и раньше, доступ к интерфейсу StorageManager осуществляется через свойство navigator.storage .

async function persistData() {
  if (navigator.storage && navigator.storage.persist) {
    const result = await navigator.storage.persist();
    console.log(`Data persisted: ${result}`);
}

Вы также можете проверить, предоставлено ли уже постоянное хранилище в текущем источнике, вызвав StorageManager.persisted() . Firefox запрашивает у пользователя разрешение на использование постоянного хранилища. Браузеры на базе Chromium предоставляют или запрещают сохранение на основе эвристики , определяющей важность контента для пользователя. Одним из критериев Google Chrome является, например, установка PWA. Если пользователь установил значок PWA в операционную систему, браузер может предоставить постоянное хранилище.

Mozilla Firefox запрашивает у пользователя разрешение на сохранение хранилища.

Поддержка API-браузера

Веб-хранилище

Поддержка браузера

  • Хром: 4.
  • Край: 12.
  • Фаерфокс: 3.5.
  • Сафари: 4.

Источник

Доступ к файловой системе

Поддержка браузера

  • Хром: 86.
  • Край: 86.
  • Фаерфокс: 111.
  • Сафари: 15.2.

Источник

Менеджер хранилища

Поддержка браузера

  • Хром: 55.
  • Край: 79.
  • Фаерфокс: 57.
  • Сафари: 15.2.

Источник

Ресурсы