Per creare un'esperienza offline solida, la tua PWA ha bisogno di gestire lo spazio di archiviazione. Nel capitolo sulla memorizzazione nella cache hai imparato che lo spazio di archiviazione nella cache è un'opzione per salvare i dati su un dispositivo. In questo capitolo ti mostreremo come gestire i dati offline, tra cui la persistenza dei dati, i limiti e gli strumenti disponibili.
Archiviazione
Lo spazio di archiviazione non riguarda solo file e asset, ma può includere altri tipi di dati. Su tutti i browser che supportano le PWA, sono disponibili le seguenti API per l'archiviazione sul dispositivo:
- IndexedDB: un'opzione di archiviazione di oggetti NoSQL per dati strutturati e BLOB (dati binari).
- WebStorage: un modo per archiviare coppie di stringhe chiave/valore utilizzando la memoria locale o di sessione. Non è disponibile all'interno di un contesto di service worker. Questa API è sincrona, pertanto non è consigliata per l'archiviazione di dati complessi.
- Spazio di archiviazione della cache: come illustrato nel modulo Memorizzazione nella cache.
Puoi gestire tutto lo spazio di archiviazione del dispositivo con l'API Storage Manager sulle piattaforme supportate. L'API Cache Storage e IndexedDB forniscono accesso asincrono allo spazio di archiviazione permanente per le PWA e sono accessibili dal thread principale, dai web worker e dai service worker. Entrambi svolgono un ruolo essenziale nel far funzionare in modo affidabile le PWA quando la rete è instabile o inesistente. Ma quando utilizzarli?
Utilizza l'API Cache Storage per le risorse di rete e gli elementi a cui puoi accedere, richiedendoli tramite un URL, ad esempio HTML, CSS, JavaScript, immagini, video e audio.
Utilizza IndexedDB per archiviare dati strutturati. Sono inclusi i dati che devono essere disponibili per la ricerca o combinabili in modo simile a NoSQL o altri dati come i dati specifici dell'utente che non corrispondono necessariamente a una richiesta di URL. Tieni presente che IndexedDB non è progettato per la ricerca a testo intero.
IndexedDB
Per utilizzare IndexedDB, devi prima aprire un database. Viene creato un nuovo database, se non ne esiste uno.
IndexedDB è un'API asincrona, ma richiede un callback invece di restituire una Promise. L'esempio seguente utilizza la libreria idb di Jake Archibald, che è un piccolo wrapper Promise per IndexedDB. Le librerie helper non sono necessarie per utilizzare IndexedDB, ma se vuoi utilizzare la sintassi Promise, puoi utilizzare la libreria idb
.
L'esempio seguente crea un database per conservare le ricette di cucina.
Creazione e apertura di un database
Per aprire un database:
- Usa la funzione
openDB
per creare un nuovo database IndexedDB denominatocookbook
. Poiché i database IndexedDB vengono sottoposti al controllo delle versioni, devi aumentare il numero di versione ogni volta che apporti modifiche alla struttura del database. Il secondo parametro è la versione del database. Nell'esempio è impostato su 1. - A
openDB()
viene passato un oggetto di inizializzazione contenente un callbackupgrade()
. La funzione di callback viene chiamata quando il database viene installato per la prima volta o quando viene eseguito l'upgrade a una nuova versione. Questa funzione è l'unica posizione in cui possono verificarsi azioni. Le azioni potrebbero includere la creazione di nuovi archivi di oggetti (le strutture utilizzate da IndexedDB per organizzare i dati) o indici (su cui vuoi eseguire ricerche). È qui che deve avvenire la migrazione dei dati. In genere, la funzioneupgrade()
contiene un'istruzioneswitch
senza istruzionibreak
per consentire il verificarsi di ogni passaggio in ordine, in base alla versione precedente del database.
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');
}
}
});
}
L'esempio crea un archivio di oggetti all'interno del database cookbook
denominato recipes
, con la proprietà id
impostata come chiave di indice del negozio e crea un altro indice denominato type
, basato sulla proprietà type
.
Diamo un'occhiata all'archivio di oggetti appena creato. Ecco cosa dovrebbe accadere dopo aver aggiunto formule all'archivio di oggetti e aver aperto DevTools nei browser basati su Chromium o in Inspector web su Safari:
Aggiunta di dati
IndexedDB utilizza le transazioni. Le transazioni raggruppano le azioni, quindi si verificano come unità. Aiutano a garantire che il database sia sempre in uno stato coerente. Sono inoltre fondamentali, se hai più copie della tua app in esecuzione, per impedire la scrittura simultanea sugli stessi dati. Per aggiungere dati:
- Avvia una transazione impostando
mode
sureadwrite
. - Ottieni l'archivio di oggetti in cui aggiungerai i dati.
- Chiama
add()
con i dati che stai salvando. Il metodo riceve i dati sotto forma di dizionario (come coppie chiave/valore) e li aggiunge all'archivio di oggetti. Il dizionario deve essere clonato tramite la clonazione strutturata. Se vuoi aggiornare un oggetto esistente, devi chiamare il metodoput()
.
Le transazioni hanno una promessa done
che si risolve quando la transazione viene completata correttamente o viene rifiutata con un errore di transazione.
Come spiega la documentazione della libreria IDB, se stai scrivendo nel database, tx.done
è l'indicatore che il commit di tutto è stato eseguito correttamente nel database. Tuttavia, è utile attendere le singole operazioni in modo da poter visualizzare gli eventuali errori che causano la mancata riuscita della transazione.
// 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;
}
Una volta aggiunti i biscotti, la ricetta sarà presente nel database insieme ad altre ricette. L'ID viene impostato e incrementato automaticamente da indicizzatoDB. Se esegui questo codice due volte, avrai due voci di cookie identiche.
Recupero dei dati in corso…
Ecco come ottenere i dati da IndexedDB:
- Avvia una transazione e specifica l'archivio o gli archivi di oggetti e, facoltativamente, il tipo di transazione.
- Chiama
objectStore()
da quella transazione. Assicurati di specificare il nome dell'archivio di oggetti. - Chiama
get()
con la chiave che vuoi ricevere. Per impostazione predefinita, l'archivio usa la propria chiave come indice.
// 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]);
}
Gestione archiviazione
Sapere come gestire lo spazio di archiviazione della PWA è particolarmente importante per memorizzare e trasmettere correttamente le risposte della rete in modalità flusso.
La capacità di archiviazione è condivisa tra tutte le opzioni di archiviazione, tra cui Archiviazione cache, IndexedDB, Web Storage e persino il file del service worker e le sue dipendenze.
Tuttavia, la quantità di spazio di archiviazione disponibile varia da browser a browser. con molta probabilità di esaurirlo. i siti potevano archiviare megabyte e persino gigabyte di dati su alcuni browser. Ad esempio, Chrome consente al browser di utilizzare fino all'80% dello spazio totale su disco e una singola origine può utilizzare fino al 60% dello spazio su disco completo. Per i browser che supportano l'API Storage, puoi sapere quanto spazio di archiviazione è ancora disponibile per la tua app, la sua quota e il suo utilizzo.
L'esempio seguente utilizza l'API Storage per ottenere una stima della quota e dell'utilizzo, quindi calcola la percentuale utilizzata e i byte rimanenti. Tieni presente che navigator.storage
restituisce un'istanza di StorageManager
. Esiste un'interfaccia Storage
separata ed è facile confondersi.
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 Chromium DevTools puoi vedere la quota del tuo sito e la quantità di spazio di archiviazione utilizzata suddivisa in base al suo utilizzo, aprendo la sezione Spazio di archiviazione nella scheda Applicazione.
Firefox e Safari non offrono una schermata di riepilogo per visualizzare la quota di spazio di archiviazione e l'utilizzo per l'origine corrente.
Persistenza dei dati
Puoi richiedere al browser l'archiviazione permanente sulle piattaforme compatibili per evitare l'eliminazione automatica dei dati dopo inattività o con utilizzo intensivo dello spazio di archiviazione. Se viene concesso, il browser non rimuoverà mai i dati dallo spazio di archiviazione. Questa protezione include la registrazione dei service worker, i database IndexedDB e i file nello spazio di archiviazione della cache. Tieni presente che gli utenti sono sempre il responsabile e possono eliminare lo spazio di archiviazione in qualsiasi momento, anche se il browser ha concesso l'archiviazione permanente.
Per richiedere l'archiviazione permanente, chiama StorageManager.persist()
. Come in precedenza, l'accesso all'interfaccia StorageManager
avviene tramite la proprietà navigator.storage
.
async function persistData() {
if (navigator.storage && navigator.storage.persist) {
const result = await navigator.storage.persist();
console.log(`Data persisted: ${result}`);
}
Puoi anche controllare se l'archiviazione permanente è già concessa nell'origine attuale chiamando StorageManager.persisted()
. Firefox richiede all'utente l'autorizzazione per utilizzare l'archiviazione permanente. I browser basati su Chromium danno o negano la persistenza in base a un'euristica per determinare l'importanza dei contenuti per l'utente. Un criterio per Google Chrome è, ad esempio, l'installazione della PWA. Se l'utente ha installato un'icona per la PWA nel sistema operativo, il browser potrebbe concedere l'archiviazione permanente.
Supporto del browser API
Archiviazione web
Accesso al file system
Gestione archiviazione