Dane offline

Aby zapewnić komfort używania offline, Twoja aplikacja PWA wymaga zarządzania miejscem na dane. W rozdziale dotyczącym buforowania omówiliśmy, że pamięć podręczna to jedna z opcji zapisu danych na urządzeniu. W tym rozdziale pokażemy, jak zarządzać danymi offline, w tym trwałością danych i limitami, a także dostępne narzędzia.

Pamięć masowa to nie tylko pliki i zasoby, ale również inne typy danych. W przypadku wszystkich przeglądarek obsługujących aplikacje PWA na urządzeniu dostępne są te interfejsy API:

  • IndexedDB: opcja obiektowej pamięci masowej NoSQL na potrzeby uporządkowanych danych i obiektów blob (danych binarnych).
  • WebStorage: sposób przechowywania par ciągu znaków klucz-wartość z wykorzystaniem pamięci lokalnej lub pamięci sesji. Nie jest dostępny w kontekście skryptu service worker. Ten interfejs API jest synchroniczny, więc nie jest zalecany do złożonego przechowywania danych.
  • Pamięć podręczna: zgodnie z opisem w module Pamięć podręczna.
.

Całą pamięcią urządzeń możesz zarządzać za pomocą interfejsu Storage Manager API na obsługiwanych platformach. Interfejs Cache Storage API i IndexedDB zapewniają asynchroniczny dostęp do trwałej pamięci masowej dla aplikacji PWA. Dostęp do nich można uzyskiwać z poziomu wątku głównego, instancji roboczych sieciowych i instancji roboczych usługi. Obie usługi odgrywają kluczową rolę w zapewnieniu niezawodnego działania aplikacji PWA, gdy sieć jest niestabilna lub nie istnieje. Kiedy jednak warto je stosować?

Używaj interfejsu Cache Storage API do obsługi zasobów sieciowych, czyli elementów, do których chcesz uzyskać dostęp za pomocą adresu URL, na przykład kodu HTML, CSS, JavaScript, obrazów, filmów i plików audio.

Używaj metody IndexedDB do przechowywania uporządkowanych danych. Dotyczy to danych, które muszą być możliwe do wyszukiwania lub łączenia w sposób podobny do NoSQL, a także inne dane, takie jak dane dotyczące użytkowników, które nie muszą być zgodne z żądaniem adresu URL. Pamiętaj, że interfejs IndexedDB nie jest przeznaczony do wyszukiwania pełnotekstowego.

IndexedDB

Aby użyć modelu IndexedDB, najpierw otwórz bazę danych. Spowoduje to utworzenie nowej bazy danych, jeśli taka nie istnieje. IndexedDB to asynchroniczny interfejs API, który wykonuje wywołanie zwrotne zamiast obietnicy. Poniższy przykład korzysta z biblioteki idb Jake'a Archibalda, która jest niewielkim otokiem Promise dla IndexedDB. Biblioteki pomocnicze nie są wymagane do korzystania z IndexedDB, ale jeśli chcesz używać składni Promise, możesz skorzystać z biblioteki idb.

W przykładzie poniżej tworzysz bazę danych do przechowywania przepisów kulinarnych.

Tworzenie i otwieranie bazy danych

Aby otworzyć bazę danych:

  1. Użyj funkcji openDB, aby utworzyć nową bazę danych IndexedDB o nazwie cookbook. Bazy danych IndexedDB mają różne wersje, więc za każdym razem, gdy wprowadzasz zmiany w strukturze bazy danych, musisz zwiększać numer wersji. Drugi parametr to wersja bazy danych. W tym przykładzie ma on wartość 1.
  2. Obiekt inicjowania zawierający wywołanie zwrotne upgrade() jest przekazywany do openDB(). Funkcja wywołania zwrotnego jest wywoływana podczas pierwszej instalacji bazy danych lub przy jej uaktualnieniu do nowej wersji. Jest to jedyne miejsce, w którym mogą być wykonywane działania. Działaniami mogą być utworzenie nowych magazynów obiektów (struktur, których IndexedDB używa do porządkowania danych) lub indeksów (które chcesz wyszukiwać). W tym miejscu powinna odbyć się migracja danych. Zwykle funkcja upgrade() zawiera instrukcję switch bez instrukcji break, która umożliwia wykonanie każdego kroku w odpowiedniej kolejności zgodnie ze starszą wersją bazy danych.
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');
     }
   }
  });
}

W tym przykładzie tworzymy magazyn obiektów w bazie danych cookbook o nazwie recipes, z właściwością id ustawioną jako klucz indeksu sklepu i z kolejnym indeksem o nazwie type na podstawie właściwości type.

Spójrzmy na nowo utworzoną magazyn obiektów. Po dodaniu przepisów do magazynu obiektów i otworzeniu Narzędzi deweloperskich w przeglądarkach opartych na Chromium lub Inspektora sieci w Safari możesz zobaczyć następujące wyniki:

Przeglądarka Safari i Chrome pokazująca zawartość IndexedDB.

Dodaję dane

IndexedDB korzysta z transakcji. Transakcje łączą działania w taki sposób, aby tworzyły je razem. Pomagają zadbać o to, aby baza danych miała zawsze spójny stan. Są one również krytyczne, jeśli masz uruchomionych wiele kopii aplikacji, ponieważ pozwala uniknąć jednoczesnego zapisywania tych samych danych. Aby dodać dane:

  1. Rozpocznij transakcję, używając opcji mode ustawionej na readwrite.
  2. Pobierz magazyn obiektów, do którego chcesz dodać dane.
  3. Zadzwoń pod numer add(), używając zachowywanych danych. Metoda odbiera dane w postaci słownika (w postaci par klucz/wartość) i dodaje je do magazynu obiektów. Słownik musi być sklonowany za pomocą klonowania uporządkowanych danych. Jeśli chcesz zaktualizować istniejący obiekt, wywołaj metodę put().

Transakcje zawierają obietnicę done opłacaną po pomyślnym sfinalizowaniu transakcji lub są odrzucane z powodu błędu transakcji.

Zgodnie z dokumentacją biblioteki IDB, jeśli piszesz do bazy danych, tx.done to sygnał, że wszystko zostało w niej zatwierdzone. Warto jednak poczekać na poszczególne operacje, aby zobaczyć wszystkie błędy, które spowodowały niepowodzenie transakcji.

// 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;
}

Gdy dodasz pliki cookie, przepis znajdzie się w bazie danych razem z innymi przepisami. Identyfikator jest ustawiany automatycznie i zwiększany przez usługę indexDB. Jeśli uruchomisz ten kod dwukrotnie, otrzymasz dwa identyczne wpisy o plikach cookie.

Pobieram dane

Aby pobrać dane z IndexedDB:

  1. Rozpocznij transakcję i określ magazyn obiektów lub magazyny oraz opcjonalnie typ transakcji.
  2. Wywołaj z tej transakcji numer objectStore(). Pamiętaj, aby podać nazwę magazynu obiektów.
  3. Zadzwoń pod numer get() z kluczem, który chcesz otrzymać. Domyślnie magazyn używa swojego klucza jako indeksu.
// 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]);
}

Menedżer miejsca

Umiejętność zarządzania pamięcią aplikacji PWA jest szczególnie ważna dla prawidłowego przechowywania i strumieniowego przesyłania odpowiedzi sieciowych.

Rozmiar miejsca na dane jest współużytkowany przez wszystkie opcje pamięci masowej, w tym Cache Storage, IndexedDB, Web Storage, a nawet plik skryptu service worker i jego zależności. Ilość dostępnego miejsca różni się jednak w zależności od przeglądarki. raczej nie wyczerpią się treści. niektóre przeglądarki mogą przechowywać megabajty, a nawet gigabajty danych. Na przykład Chrome umożliwia przeglądarce wykorzystanie do 80% miejsca na dysku, a pojedyncze źródło może zająć do 60% całego miejsca na dysku. W przypadku przeglądarek, które obsługują interfejs Storage API, możesz sprawdzić ilość miejsca dostępnego dla aplikacji, jej limit oraz sposób jej wykorzystania. Poniższy przykład wykorzystuje interfejs Storage API do uzyskania szacowanego limitu i wykorzystania, a następnie oblicza procent wykorzystania i pozostałe bajty. Pamiętaj, że funkcja navigator.storage zwraca instancję StorageManager. Mają osobny interfejs Storage, który można łatwo pomylić.

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.`);
}

W Narzędziach deweloperskich w Chromium możesz sprawdzić limit miejsca w witrynie oraz ilość zajętego miejsca według używanych funkcji. Aby to zrobić, otwórz sekcję Miejsce na dane na karcie Aplikacja.

Narzędzia deweloperskie w Chrome w aplikacji, sekcja Wyczyść pamięć

W przeglądarkach Firefox i Safari nie ma ekranu z podsumowaniem, na którym widać cały limit i wykorzystanie miejsca na dane w bieżącym źródle.

Trwałość danych

Możesz poprosić przeglądarkę o przechowywanie pamięci trwałej na zgodnych platformach, aby uniknąć automatycznego usuwania danych po braku aktywności lub gdy brakuje miejsca na dane. Jeśli przyznasz to uprawnienie, przeglądarka nigdy nie usunie danych z pamięci. Ochrona obejmuje rejestrację skryptu service worker, bazy danych IndexedDB i pliki w pamięci podręcznej. Pamiętaj, że to użytkownicy zawsze mają kontrolę nad swoimi danymi i w każdej chwili mogą usunąć pamięć, nawet jeśli przeglądarka przyznała pamięć trwałą.

Aby zażądać pamięci trwałej, wywołaj StorageManager.persist(). Tak jak wcześniej, dostęp do interfejsu StorageManager można uzyskać przez usługę navigator.storage.

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

Możesz też sprawdzić, czy w bieżącym źródle została już przyznana pamięć trwała, wywołując funkcję StorageManager.persisted(). Firefox prosi użytkownika o pozwolenie na użycie pamięci trwałej. Przeglądarki oparte na Chromium określają znaczenie treści dla użytkownika na podstawie heurystyki (na podstawie tych wartości lub je odrzucają). Jednym z kryteriów stosowanych w Google Chrome jest na przykład instalacja PWA. Jeśli użytkownik zainstalował ikonę PWA w systemie operacyjnym, przeglądarka może przyznać trwałą pamięć masową.

Mozilla Firefox prosi użytkownika o przyznanie uprawnień do przechowywania danych.

.

Obsługa przeglądarek z interfejsami API

Przechowywanie danych w internecie

Obsługa przeglądarek

  • Chrome: 4.
  • Krawędź: 12.
  • Firefox: 3.5
  • Safari: 4.

Źródło

Dostęp do systemu plików

Obsługa przeglądarek

  • Chrome: 86.
  • Edge: 86.
  • Firefox: 111.
  • Safari: 15.2

Źródło

Menedżer miejsca

Obsługa przeglądarek

  • Chrome: 55.
  • Krawędź: 79.
  • Firefox: 57.
  • Safari: 15.2

Źródło

Zasoby