Dane offline

Aby zapewnić użytkownikom płynne działanie aplikacji offline, musisz zarządzać miejscem na dane w PWA. W rozdziale poświęconym pamięci podręcznej dowiedziałeś się, że pamięć podręczna to jeden ze sposobów zapisywania danych na urządzeniu. W tym rozdziale pokażemy Ci, jak zarządzać danymi offline, w tym trwałością danych, limitami i dostępnymi narzędziami.

Miejsce na dane

Pamięć nie służy tylko do przechowywania plików i zasobów, ale może zawierać inne typy danych. W przypadku wszystkich przeglądarek obsługujących Progressive Web Apps dostępne są te interfejsy API do przechowywania danych na urządzeniu:

  • IndexedDB opcja przechowywania obiektów NoSQL na potrzeby danych ustrukturyzowanych i blobów (danych binarnych).
  • WebStorage: sposób przechowywania par klucz-wartość za pomocą pamięci lokalnej lub pamięci sesji. Nie jest ona dostępna w kontekście skryptu service worker. Ten interfejs API jest synchroniczny, więc nie zalecamy jego używania do złożonego przechowywania danych.
  • Pamięć podręczna: omówiona w module na temat pamięci podręcznej.

Możesz zarządzać całym miejscem na dane urządzenia za pomocą interfejsu Storage Manager API na obsługiwanych platformach. Interfejs Cache Storage API i IndexedDB zapewniają asynchroniczny dostęp do trwałego magazynu danych w przypadku aplikacji PWA. Można z nich korzystać z głównego wątku, workerów internetowych i workerów usługowych. Oba te elementy odgrywają kluczową rolę w zapewnianiu niezawodnego działania PWA, gdy sieć jest niestabilna lub nie istnieje. Kiedy należy ich używać?

Użyj interfejsu Cache Storage API do zasobów sieciowych, czyli elementów, do których dostęp uzyskujesz, wysyłając żądanie za pomocą adresu URL, takich jak HTML, CSS, JavaScript, obrazy, filmy i dźwięk.

Do przechowywania uporządkowanych danych używaj IndexedDB. Obejmuje to dane, które można wyszukiwać lub łączyć w sposób podobny do NoSQL, a także inne dane, np. dane o użytkownikach, które niekoniecznie pasują do żądania adresu URL. Pamiętaj, że IndexedDB nie jest przeznaczony do wyszukiwania pełnotekstowego.

IndexedDB

Aby korzystać z IndexedDB, najpierw otwórz bazę danych. Spowoduje to utworzenie nowej bazy danych, jeśli nie istnieje. IndexedDB to interfejs API asynchroniczny, ale zamiast zwracać obietnicę, wywołuje funkcję wywołania zwrotnego. W tym przykładzie korzystamy z biblioteki idb Jake'a Archibalda, która jest małą obudową Promise dla IndexedDB. Korzystanie z IndexedDB nie wymaga bibliotek pomocniczych, ale jeśli chcesz używać składni obietnicy, możesz skorzystać z biblioteki idb.

W tym przykładzie tworzymy 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 są wersjonowane, więc po wprowadzeniu zmian w strukturze bazy danych musisz zwiększyć numer wersji. Drugi parametr to wersja bazy danych. W tym przykładzie ma wartość 1.
  2. Obiekt inicjalizacji zawierający funkcję wywołania zwrotnego upgrade() jest przekazywany do openDB(). Funkcja wywołania zwrotnego jest wywoływana, gdy baza danych jest instalowana po raz pierwszy lub gdy jest aktualizowana do nowej wersji. Ta funkcja jest jedynym miejscem, w którym mogą występować działania. Działania te mogą obejmować tworzenie nowych magazynów obiektów (struktur IndexedDB służących do organizowania danych) lub indeksów (które chcesz wyszukiwać). W tym miejscu powinna też nastąpić migracja danych. Zwykle funkcja upgrade() zawiera instrukcję switch bez instrukcji break, aby umożliwić wykonywanie poszczególnych kroków w kolejności, w zależności od tego, jaką jest stara wersja 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 w ramach bazy danych cookbook magazyn obiektów o nazwie recipes, w którym kluczem indeksu jest usługa id. Na podstawie tej usługi tworzymy też inny indeks o nazwie type.type

Spójrzmy na utworzony właśnie obiekt. Po dodaniu receptur do repozytorium obiektów i otwarciu DevTools w przeglądarkach opartych na Chromium lub Web Inspector w Safari powinno być widoczne to:

Safari i Chrome wyświetlają zawartość IndexedDB.

Dodawanie danych

IndexedDB korzysta z transakcji. Transakcje grupują działania, dzięki czemu są one wykonywane jako całość. Pomagają one zapewnić, aby baza danych była zawsze spójna. Jeśli masz uruchomionych kilka kopii aplikacji, są one też bardzo ważne, ponieważ zapobiegają jednoczesnemu zapisywaniu tych samych danych. Aby dodać dane:

  1. Rozpocznij transakcję z wartością parametru mode ustawioną na readwrite.
  2. Otwórz obiektowy magazyn danych, do którego dodasz dane.
  3. Zadzwoń pod numer add(), podając dane, które chcesz zapisać. Metoda otrzymuje dane w postaci słownika (jako pary klucz-wartość) i dodaje je do magazynu obiektów. Słownik musi umożliwiać klonowanie za pomocą strukturalnego klonowania. Jeśli chcesz zaktualizować istniejący obiekt, wywołaj zamiast tego metodę put().

Transakcje mają obietnicę done, która jest spełniana, gdy transakcja zostanie zakończona, lub odrzucana z powodu błędu transakcji.

Jak wyjaśniono w dokumentacji biblioteki IDB, jeśli zapisujesz dane w bazie danych, sygnał tx.done oznacza, że wszystko zostało pomyślnie zapisane w bazie danych. Zalecamy jednak, aby poczekać na poszczególne operacje, aby można było zobaczyć błędy, które powodują 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 zostanie umieszczony w bazie danych razem z innymi przepisami. Identyfikator jest automatycznie ustawiany i zwiększany przez indexedDB. Jeśli uruchomisz ten kod 2 razy, będą 2 identyczne wpisy plików cookie.

Pobieram dane

Aby pobrać dane z IndexedDB:

  1. Rozpocznij transakcję i określ magazyny obiektów lub opcjonalnie typ transakcji.
  2. Zadzwoń do objectStore() z tej transakcji. Pamiętaj, aby podać nazwę repozytorium obiektów.
  3. Zadzwoń pod numer get(), podając klucz, który chcesz otrzymać. Domyślnie sklep używa 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ą PWA jest szczególnie ważna w przypadku prawidłowego przechowywania i przesyłania strumieniowego odpowiedzi sieci.

Pojemność pamięci jest współdzielona między wszystkimi opcjami pamięci, w tym pamięci podręcznej, IndexedDB, pamięci internetowej, a nawet plikiem usługi i jego zależnościami. Ilość dostępnego miejsca różni się jednak w zależności od przeglądarki. Nie powinno się to zdarzyć, ponieważ w niektórych przeglądarkach strony mogą przechowywać megabajty, a nawet gigabajty danych. Chrome na przykład pozwala przeglądarce używać do 80% łącznej ilości miejsca na dysku, a pojedynczy adres domeny może zajmować do 60% całego miejsca na dysku. W przypadku przeglądarek obsługujących interfejs Storage API możesz sprawdzić, ile miejsca na dane jest jeszcze dostępne dla Twojej aplikacji, jej limit i wykorzystanie. W tym przykładzie interfejs Storage API służy do szacowania limitu i wykorzystania, a następnie do obliczania procenta wykorzystanych i pozostałych bajtów. Pamiętaj, że navigator.storage zwraca instancję StorageManager. Istnieje osobny interfejs Storage, który łatwo pomylić z Google Analytics.

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 Chromium DevTools możesz wyświetlić przydział witryny i wykorzystanie miejsca na dane z podziałem na elementy, otwierając sekcję Miejsce na dane na karcie Aplikacja.

Narzędzia deweloperskie w Chrome, sekcja Aplikacja, Wyczyść pamięć masową

Firefox i Safari nie mają ekranu podsumowania, na którym można zobaczyć całą pulę miejsca na dane i jego wykorzystanie w przypadku bieżącego źródła.

Trwałość danych

Aby uniknąć automatycznego usuwania danych po okresie nieaktywności lub w przypadku braku miejsca na dane, możesz poprosić przeglądarkę o trwałe miejsce na dane na zgodnych platformach. Jeśli zgoda zostanie udzielona, przeglądarka nigdy nie usunie danych z pamięci. Ta ochrona obejmuje rejestrację usług, bazy danych IndexedDB i pliki w pamięci podręcznej. Pamiętaj, że użytkownicy mają zawsze kontrolę i mogą w każdej chwili usunąć dane, nawet jeśli przeglądarka przyznała trwałe miejsce na dane.

Aby poprosić o stałe miejsce na dane, zadzwoń pod numer StorageManager.persist(). Podobnie jak wcześniej, interfejs StorageManager jest dostępny w usłudze 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 trwałe miejsce na dane zostało już przyznane w bieżącym źródle, wywołując funkcję StorageManager.persisted(). Firefox prosi użytkownika o pozwolenie na używanie trwałego miejsca na dane. Przeglądarki oparte na Chromium zezwalają na trwałość lub odmawiają jej na podstawie heurystyki, która określa znaczenie treści dla użytkownika. Jednym z kryteriów w przypadku Google Chrome jest na przykład instalacja PWA. Jeśli użytkownik zainstalował ikonę PWA w systemie operacyjnym, przeglądarka może przyznać trwałe miejsce na dane.

Mozilla Firefox prosi użytkownika o zezwolenie na trwałe przechowywanie danych.

Obsługa przeglądarki API

Magazyn danych w internecie

Browser Support

  • Chrome: 4.
  • Edge: 12.
  • Firefox: 3.5.
  • Safari: 4.

Source

Dostęp do systemu plików

Browser Support

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

Source

Menedżer miejsca

Browser Support

  • Chrome: 55.
  • Edge: 79.
  • Firefox: 57.
  • Safari: 15.2.

Source

Zasoby