Dane offline

Aby aplikacja PWA działała bez zakłóceń, musisz zarządzać miejscem na dane. W rozdziale dotyczącym buforowania wiesz, że jedną z opcji zapisywania danych na urządzeniu jest pamięć podręczna. W tym rozdziale pokażemy, jak zarządzać danymi offline, w tym ich trwałością, limitami i dostępnymi narzędziami.

Miejsce na dane

Miejsce na dane to nie tylko pliki i zasoby, ale także inne rodzaje danych. We wszystkich przeglądarkach, które obsługują 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 klucz-wartość w postaci ciągu znaków z wykorzystaniem pamięci lokalnej lub pamięci sesji. Nie jest dostępna w kontekście skryptu service worker. Ten interfejs API jest synchroniczny, więc nie jest zalecany w przypadku przechowywania złożonych danych.
  • Pamięć podręczna: jak omówiono w module pamięci podręcznej.

Całą pamięcią na urządzeniu 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 dla PWA. Dostęp do nich można uzyskiwać z wątku głównego, instancji roboczych internetowych i mechanizmów Service Worker. Obie te usługi odgrywają kluczową rolę w tworzeniu aplikacji PWA, które działają niezawodnie, gdy sieć działa niestabilnie lub nie istnieje. Kiedy jednak warto ich używać?

Interfejs Cache Storage API jest przeznaczony do obsługi zasobów sieciowych, czyli rzeczy, do których można uzyskać dostęp za pomocą żądania adresu URL, np. HTML, CSS, JavaScript, obrazy, filmy i dźwięk.

Do przechowywania uporządkowanych danych używaj IndexedDB. Obejmuje to dane, które muszą być dostępne do przeszukiwania lub łączenie w sposób podobny do NoSQL, a także inne dane, takie jak dane specyficzne dla użytkownika, które niekoniecznie muszą odpowiadać żądaniu adresu URL. Pamiętaj, że platforma IndexedDB nie jest przeznaczona do wyszukiwania pełnotekstowego.

IndexedDB

Aby użyć IndexedDB, najpierw otwórz bazę danych. Spowoduje to utworzenie nowej bazy danych, jeśli taka nie istnieje. IndexedDB to asynchroniczny interfejs API, ale wymaga wywołania zwrotnego zamiast zwracać obietnicę. W tym przykładzie użyto biblioteki IDb Jake'a Archibalda, która jest małą otoką Promise dla IndexedDB. Biblioteki pomocnicze nie są wymagane do korzystania z IndexedDB, ale jeśli chcesz używać składni Promise, możesz użyć biblioteki idb.

Poniższy przykład pokazuje utworzenie bazy 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. Ponieważ bazy danych IndexedDB są objęte obsługą wersji, musisz zwiększać numer wersji za każdym razem, gdy wprowadzasz zmiany w strukturze bazy danych. Drugi parametr to wersja bazy danych. W przykładzie ma wartość 1.
  2. Obiekt inicjowania zawierający wywołanie zwrotne upgrade() jest przekazywany do openDB(). Funkcja wywołania zwrotnego jest wywoływana przy pierwszym zainstalowaniu bazy danych lub przy aktualizowaniu bazy danych do nowej wersji. To jedyne miejsce, w którym mogą być wykonywane działania. Działania mogą obejmować tworzenie nowych magazynów obiektów (struktur, których IndexedDB używa do porządkowania danych) lub indeksów (które chcesz przeszukać). To właśnie tam powinna nastąpić migracja danych. Zwykle funkcja upgrade() zawiera instrukcję switch bez instrukcji break, aby umożliwić wykonywanie wszystkich kroków w odpowiedniej kolejności, zgodnie ze starą 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, ustawiając właściwość id jako klucz indeksu sklepu i tworzymy kolejny indeks o nazwie type na podstawie właściwości type.

Przyjrzyjmy się właśnie utworzonej bazie obiektów. Po dodaniu przepisów do magazynu obiektów i otwarciu Narzędzi deweloperskich w przeglądarkach opartych na Chromium lub inspektorze sieci w Safari zobaczysz następujące wyniki:

Safari i Chrome pokazują zawartość IndexedDB.

Dodawanie danych

Platforma IndexedDB korzysta z transakcji. Transakcje grupują działania, dzięki czemu są przedstawiane jako całość. Pomagają zapewnić, że baza danych jest zawsze w spójnym stanie. Są one również niezbędne, jeśli masz uruchomionych wiele kopii aplikacji, ponieważ uniemożliwiają one jednoczesne zapisywanie tych samych danych. Aby dodać dane:

  1. Rozpocznij transakcję z mode ustawionym na readwrite.
  2. Pobierz magazyn obiektów, do którego chcesz dodać dane.
  3. Zadzwoń pod numer add(), podając dane, które zapisujesz. Metoda otrzymuje dane w postaci słownikowej (w postaci par klucz/wartość) i dodaje je do magazynu obiektów. Słownik musi być sklonowany przy użyciu funkcji klonowania uporządkowanych. Aby zaktualizować istniejący obiekt, wywołaj metodę put().

Transakcje zawierają obietnicę typu done, która zniknie po jej pomyślnym zakończeniu lub zostanie odrzucona z powodu błędu transakcji.

Jak wyjaśnia dokumentacja biblioteki IDB, jeśli zapisujesz w bazie danych, tx.done jest sygnałem, że wszystkie elementy zostały w niej zatwierdzone. Warto jednak poczekać na poszczególne operacje, aby zobaczyć ewentualne błędy, które mogły spowodować 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;
}

Po dodaniu ciasteczek przepis znajdzie się w bazie danych razem z innymi przepisami. Identyfikator jest automatycznie ustawiany i zwiększany przez indexDB. Jeśli uruchomisz ten kod dwukrotnie, otrzymasz 2 identyczne wpisy dotyczące plików cookie.

Pobieram dane

Oto jak uzyskać dane z IndexedDB:

  1. Rozpocznij transakcję i określ magazyn obiektów oraz opcjonalnie typ transakcji.
  2. Zadzwoń na numer objectStore() z tej transakcji. Pamiętaj, by podać nazwę magazynu obiektów.
  3. Zadzwoń pod numer get(), podając klucz, 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

Wiedza na temat zarządzania miejscem na dane PWA jest szczególnie ważna, jeśli chcesz prawidłowo przechowywać i przesyłać odpowiedzi sieci.

Pojemność miejsca na dane jest współużytkowana przez wszystkie opcje pamięci podręcznej, 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. Najprawdopodobniej nie zabraknie Ci miejsca. W niektórych przeglądarkach witryny mogą przechowywać megabajty, a nawet gigabajty danych. Na przykład Chrome pozwala przeglądarce wykorzystać do 80% całkowitej ilości 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ć, ile miejsca na dane jest nadal dostępne dla aplikacji, jaka będzie jej ilość i jakie będzie jej wykorzystanie. Poniższy przykład pokazuje użycie interfejsu Storage API do oszacowania limitu i wykorzystania, a następnie obliczy odsetek użytych i pozostałe bajty. Pamiętaj, że navigator.storage zwraca wystąpienie StorageManager. Dostępny jest osobny interfejs Storage, który łatwo się 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 otworzyć sekcję Miejsce na dane na karcie Aplikacja, aby sprawdzić wykorzystanie miejsca na dane dla witryny oraz ilość wykorzystanego miejsca z podziałem na to, co wykorzystuje.

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

Przeglądarki Firefox i Safari nie udostępniają ekranu z podsumowaniem ilości dostępnego miejsca i informacji o wykorzystaniu miejsca 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. W przypadku przyznania uprawnień przeglądarka nigdy nie usuwa danych z pamięci. To zabezpieczenie obejmuje rejestrację skryptu service worker, bazy danych IndexedDB oraz pliki w pamięci podręcznej. Pamiętaj, że to użytkownicy zawsze mają kontrolę nad miejscem na dane i mogą je usunąć w dowolnym momencie, 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 uzyskuje się przez właściwość 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 punkcie początkowym została już przyznana pamięć trwała, wywołując metodę StorageManager.persisted(). Firefox prosi użytkownika o pozwolenie na użycie pamięci trwałej. Przeglądarki oparte na Chromium oceniają ważność treści dla użytkownika na podstawie heurystyki lub odrzucają ją na podstawie danych heurystycznych. Jednym z kryteriów dotyczących Google Chrome jest instalacja aplikacji PWA. Jeśli użytkownik zainstalował ikonę PWA w systemie operacyjnym, przeglądarka może przyznać pamięć trwałą.

Mozilla Firefox wyświetla użytkownikowi prośbę o przyznanie uprawnień do trwałego przechowywania danych.

Obsługa przeglądarki API

Web Storage

Obsługa przeglądarek

  • 4
  • 12
  • 3,5
  • 4

Źródło

Dostęp do systemu plików

Obsługa przeglądarek

  • 86
  • 86
  • 111
  • 15.2

Źródło

Menedżer miejsca

Obsługa przeglądarek

  • 55
  • 79
  • 57
  • 15.2

Źródło

Zasoby