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.
Miejsce na dane
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:
- Użyj funkcji
openDB
, aby utworzyć nową bazę danych IndexedDB o nazwiecookbook
. 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. - Obiekt inicjowania zawierający wywołanie zwrotne
upgrade()
jest przekazywany doopenDB()
. 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 funkcjaupgrade()
zawiera instrukcjęswitch
bez instrukcjibreak
, 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:
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:
- Rozpocznij transakcję, używając opcji
mode
ustawionej nareadwrite
. - Pobierz magazyn obiektów, do którego chcesz dodać dane.
- 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:
- Rozpocznij transakcję i określ magazyn obiektów lub magazyny oraz opcjonalnie typ transakcji.
- Wywołaj z tej transakcji numer
objectStore()
. Pamiętaj, aby podać nazwę magazynu obiektów. - 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.
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ą.
Obsługa przeglądarek z interfejsami API
Przechowywanie danych w internecie
Dostęp do systemu plików
Menedżer miejsca