Für eine solide Offlinenutzung Ihrer PWA ist eine Speicherverwaltung erforderlich. Im Kapitel zum Caching haben Sie gelernt, dass der Cache-Speicher eine Möglichkeit ist, Daten auf einem Gerät zu speichern. In diesem Kapitel erfahren Sie, wie Sie Offlinedaten verwalten, einschließlich Datenspeicherung, Limits und verfügbaren Tools.
Speicher
Der Speicherplatz umfasst nicht nur Dateien und Assets, sondern kann auch andere Datentypen umfassen. In allen Browsern, die PWAs unterstützen, sind die folgenden APIs für den lokalen Speicher verfügbar:
- IndexedDB: Eine NoSQL-Objektspeicheroption für strukturierte Daten und Blobs (binäre Daten).
- WebStorage: Eine Möglichkeit, Schlüssel/Wert-Stringpaare mithilfe des lokalen Speichers oder des Sitzungsspeichers zu speichern. Sie ist nicht im Kontext eines Service Workers verfügbar. Diese API ist synchron und wird daher nicht für den komplexen Datenspeicher empfohlen.
- Cache-Speicher: Wie im Modul zum Caching beschrieben.
Sie können den gesamten Gerätespeicher mit der Storage Manager API auf unterstützten Plattformen verwalten. Die Cache Storage API und IndexedDB bieten asynchronen Zugriff auf den nichtflüchtigen Speicher für PWAs und können über den Haupt-Thread, Webworker und Serviceworker aufgerufen werden. Beide spielen eine wichtige Rolle, damit PWAs zuverlässig funktionieren, wenn das Netzwerk instabil oder nicht vorhanden ist. Aber wann sollten Sie welche verwenden?
Verwenden Sie die Cache Storage API für Netzwerkressourcen, auf die Sie über eine URL zugreifen, z. B. HTML, CSS, JavaScript, Bilder, Videos und Audio.
Verwenden Sie IndexedDB, um strukturierte Daten zu speichern. Dazu gehören Daten, die auf NoSQL-ähnliche Weise suchbar oder kombinierbar sein müssen, oder andere Daten wie nutzerspezifische Daten, die nicht unbedingt mit einer URL-Anfrage übereinstimmen. IndexedDB ist nicht für die Volltextsuche konzipiert.
IndexedDB
Wenn Sie IndexedDB verwenden möchten, müssen Sie zuerst eine Datenbank öffnen. Falls noch keine Datenbank vorhanden ist, wird eine neue erstellt.
IndexedDB ist eine asynchrone API, nimmt aber einen Callback entgegen, anstatt ein Promise zurückzugeben. Im folgenden Beispiel wird die idb-Bibliothek von Jake Archibald verwendet, ein kleiner Promise-Wrapper für IndexedDB. Hilfsbibliotheken sind für die Verwendung von IndexedDB nicht erforderlich. Wenn Sie jedoch die Promise-Syntax verwenden möchten, ist die idb
-Bibliothek eine Option.
Im folgenden Beispiel wird eine Datenbank zum Speichern von Kochrezepten erstellt.
Datenbank erstellen und öffnen
So öffnen Sie eine Datenbank:
- Verwenden Sie die Funktion
openDB
, um eine neue IndexedDB-Datenbank namenscookbook
zu erstellen. Da IndexedDB-Datenbanken versioniert sind, müssen Sie die Versionsnummer erhöhen, wenn Sie Änderungen an der Datenbankstruktur vornehmen. Der zweite Parameter ist die Datenbankversion. Im Beispiel ist er auf „1“ festgelegt. - Ein Initialisierungsobjekt mit einem
upgrade()
-Rückruf wird anopenDB()
übergeben. Die Rückruffunktion wird aufgerufen, wenn die Datenbank zum ersten Mal installiert oder auf eine neue Version aktualisiert wird. Nur hier können Aktionen ausgeführt werden. Zu den Aktionen gehören beispielsweise das Erstellen neuer Objektspeicher (die Strukturen, mit denen IndexedDB Daten organisiert) oder Indexe (in denen Sie suchen möchten). Hier sollte auch die Datenmigration stattfinden. Normalerweise enthält dieupgrade()
-Funktion eineswitch
-Anweisung ohnebreak
-Anweisungen, damit jeder Schritt in der Reihenfolge ausgeführt werden kann, die von der alten Version der Datenbank abhängt.
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');
}
}
});
}
Im Beispiel wird ein Objektspeicher mit dem Namen recipes
in der cookbook
-Datenbank erstellt. Die Eigenschaft id
wird als Indexschlüssel des Speichers festgelegt und ein weiterer Index namens type
wird auf der Grundlage der Eigenschaft type
erstellt.
Sehen wir uns den gerade erstellten Objektspeicher an. Nachdem Sie dem Objektspeicher Rezepte hinzugefügt und die DevTools in Chromium-basierten Browsern oder die Web-Inspektion in Safari geöffnet haben, sollte Folgendes zu sehen sein:
Daten hinzufügen
IndexedDB verwendet Transaktionen. Transaktionen gruppieren Aktionen, sodass sie als Einheit ausgeführt werden. Sie tragen dazu bei, dass die Datenbank immer in einem konsistenten Zustand ist. Sie sind auch wichtig, wenn mehrere Kopien Ihrer App ausgeführt werden, um gleichzeitiges Schreiben in dieselben Daten zu verhindern. So fügen Sie Daten hinzu:
- Starten Sie eine Transaktion mit
mode
aufreadwrite
. - Rufen Sie den Objektspeicher ab, in dem Sie Daten hinzufügen möchten.
- Rufen Sie
add()
mit den zu speichernden Daten auf. Die Methode empfängt Daten in Dictionary-Form (als Schlüssel/Wert-Paare) und fügt sie dem Objektspeicher hinzu. Das Wörterbuch muss mithilfe des strukturierten Klonens geklont werden können. Wenn Sie ein vorhandenes Objekt aktualisieren möchten, rufen Sie stattdessen die Methodeput()
auf.
Transaktionen haben ein done
-Versprechen, das erfüllt wird, wenn die Transaktion erfolgreich abgeschlossen wird, oder mit einem Transaktionsfehler abgelehnt wird.
Wie in der IDB-Bibliotheksdokumentation erläutert, ist tx.done
das Signal, dass alle Daten erfolgreich in die Datenbank geschrieben wurden. Es ist jedoch sinnvoll, auf einzelne Vorgänge zu warten, damit Sie alle Fehler sehen können, die zum Abbruch der Transaktion führen.
// 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;
}
Nachdem Sie die Cookies hinzugefügt haben, wird das Rezept zusammen mit anderen Rezepten in der Datenbank angezeigt. Die ID wird automatisch von indexedDB festgelegt und erhöht. Wenn Sie diesen Code zweimal ausführen, werden zwei identische Cookie-Einträge erstellt.
Daten werden abgerufen…
So rufen Sie Daten aus IndexedDB ab:
- Starten Sie eine Transaktion und geben Sie den Objektspeicher oder die Objektspeicher und optional den Transaktionstyp an.
- Rufe
objectStore()
über diese Transaktion auf. Geben Sie den Namen des Objektspeichers an. - Rufe
get()
mit dem gewünschten Schlüssel auf. Standardmäßig verwendet der Speicher seinen Schlüssel als Index.
// 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]);
}
Speichermanager
Das Speichern und Streamen von Netzwerkantworten ist besonders wichtig, wenn Sie den Speicher Ihrer PWA verwalten möchten.
Die Speicherkapazität wird auf alle Speicheroptionen verteilt, einschließlich Cache-Speicher, IndexedDB, Web Storage und sogar der Service Worker-Datei und ihrer Abhängigkeiten.
Die verfügbare Speicherkapazität variiert jedoch je nach Browser. Es ist unwahrscheinlich, dass Sie das Limit erreichen. Websites können in einigen Browsern mehrere Megabyte und sogar Gigabyte an Daten speichern. In Chrome kann der Browser beispielsweise bis zu 80% des gesamten Festplattenspeichers nutzen und ein einzelner Ursprung kann bis zu 60% des gesamten Festplattenspeichers belegen. In Browsern, die die Storage API unterstützen, können Sie sehen, wie viel Speicherplatz für Ihre App noch verfügbar ist, wie hoch das Kontingent ist und wie viel Speicherplatz bereits belegt ist.
Im folgenden Beispiel wird die Storage API verwendet, um das Kontingent und die Nutzung zu schätzen. Anschließend werden der Prozentsatz der Nutzung und die verbleibenden Byte berechnet. Hinweis: navigator.storage
gibt eine Instanz von StorageManager
zurück. Es gibt eine separate Storage
-Benutzeroberfläche und es ist leicht, sie zu verwechseln.
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.`);
}
In den Chromium DevTools können Sie das Kontingent Ihrer Website und die Speichernutzung aufgeschlüsselt nach den jeweiligen Elementen aufrufen. Öffnen Sie dazu auf dem Tab Application den Bereich Storage.
Firefox und Safari bieten keinen Übersichtsbildschirm, auf dem das gesamte Speicherkontingent und die gesamte Nutzung für den aktuellen Ursprung angezeigt werden.
Datenpersistenz
Sie können den Browser auf kompatiblen Plattformen um persistenten Speicher bitten, um das automatische Löschen von Daten nach Inaktivität oder bei Speichermangel zu vermeiden. Wenn die Berechtigung gewährt wird, werden Daten vom Browser nie aus dem Speicher entfernt. Dieser Schutz umfasst die Registrierung von Dienstmitarbeitern, IndexedDB-Datenbanken und Dateien im Cachespeicher. Nutzer haben immer die Kontrolle und können den Speicherplatz jederzeit löschen, auch wenn der Browser dauerhaften Speicher zugewiesen hat.
Wenn Sie nichtflüchtigen Speicher anfordern möchten, rufen Sie die StorageManager.persist()
auf. Wie bisher erfolgt der Zugriff auf die StorageManager
-Benutzeroberfläche über die Property navigator.storage
.
async function persistData() {
if (navigator.storage && navigator.storage.persist) {
const result = await navigator.storage.persist();
console.log(`Data persisted: ${result}`);
}
Sie können auch prüfen, ob der persistente Speicher bereits im aktuellen Ursprung gewährt wurde. Rufen Sie dazu StorageManager.persisted()
auf. Firefox fordert die Berechtigung des Nutzers zur Verwendung nichtflüchtigen Speichers an. Chromium-basierte Browser gewähren oder verweigern die Persistenz basierend auf einer Heuristik, um die Bedeutung der Inhalte für den Nutzer zu bestimmen. Ein Kriterium für Google Chrome ist beispielsweise die PWA-Installation. Wenn der Nutzer ein Symbol für die PWA im Betriebssystem installiert hat, gewährt der Browser möglicherweise dauerhaften Speicherplatz.
Browserunterstützung der API
Webspeicher
Dateisystemzugriff
Speichermanager