Questa guida illustra le nozioni di base dell'API IndexedDB.
Stiamo utilizzando la libreria IndexedDB Promised di Jake Archibald, molto simile all'API IndexedDB, ma che utilizza promesse, che puoi await
per una sintassi più concisa. Ciò semplifica l'API mantenendo
la struttura.
Che cos'è IndexedDB?
IndexedDB è un sistema di archiviazione NoSQL su larga scala, che consente di archiviare praticamente qualsiasi cosa nel browser dell'utente. Oltre alle consuete azioni di ricerca, get e put, IndexedDB supporta anche le transazioni ed è ideale per archiviare grandi quantità di dati strutturati.
Ogni database IndexedDB è univoco per un'origine (in genere il dominio o il sottodominio del sito), il che significa che non può accedere o essere accessibile da qualsiasi altra origine. I suoi limiti di archiviazione dei dati sono generalmente ampi, ove esistano, ma browser diversi gestiscono i limiti e l'eliminazione dei dati in modo diverso. Consulta la sezione Ulteriori informazioni per ulteriori informazioni.
Termini IndexedDB
- Database
- Il livello più alto di IndexedDB. Contiene gli archivi di oggetti, che a loro volta contengono i dati che vuoi mantenere. Puoi creare più database con qualsiasi nome tu scelga.
- Archivio di oggetti
- Un singolo bucket per l'archiviazione dei dati, in modo simile alle tabelle nei database relazionali.
In genere, è presente un archivio di oggetti per ogni tipo (non per tipo di dati JavaScript) di dati che stai archiviando. A differenza delle tabelle di database, i tipi di dati JavaScript dei dati in un datastore non devono essere coerenti. Ad esempio, se un'app ha un archivio di oggetti
people
contenente informazioni su tre persone, le proprietà relative all'età di queste persone potrebbero essere53
,'twenty-five'
eunknown
. - Indice
- Un tipo di archivio di oggetti per organizzare i dati in un altro archivio di oggetti (denominato archivio di oggetti di riferimento) in base a una singola proprietà dei dati. L'indice viene utilizzato per recuperare i record nell'archivio di oggetti da questa proprietà. Ad esempio, se vuoi salvare delle persone, potresti volerle recuperare in un secondo momento indicando il loro nome, la loro età o l'animale preferito.
- Operazione
- Un'interazione con il database.
- Transazione
- Un wrapper attorno a un'operazione o a un gruppo di operazioni che garantisce l'integrità del database. Se una delle azioni in una transazione non va a buon fine, nessuna delle azioni viene applicata e il database torna allo stato in cui si trovava prima dell'inizio della transazione. Tutte le operazioni di lettura o scrittura in IndexedDB devono far parte di una transazione. Ciò consente le operazioni di lettura, modifica e scrittura atomiche senza il rischio di conflitti con altri thread che agiscono contemporaneamente sul database.
- Cursore
- Un meccanismo per eseguire l'iterazione su più record di un database.
Come verificare il supporto di IndexedDB
IndexedDB è quasi supportato universalmente.
Tuttavia, se utilizzi browser meno recenti, non è una cattiva idea utilizzare il supporto del rilevamento delle funzionalità per ogni evenienza. Il modo più semplice è controllare l'oggetto window
:
function indexedDBStuff () {
// Check for IndexedDB support:
if (!('indexedDB' in window)) {
// Can't use IndexedDB
console.log("This browser doesn't support IndexedDB");
return;
} else {
// Do IndexedDB stuff here:
// ...
}
}
// Run IndexedDB code:
indexedDBStuff();
Come aprire un database
Con IndexedDB, puoi creare più database con qualsiasi nome tu scelga. Se
un database non esiste quando tenti di aprirlo, viene creato automaticamente.
Per aprire un database, utilizza il metodo openDB()
dalla libreria idb
:
import {openDB} from 'idb';
async function useDB () {
// Returns a promise, which makes `idb` usable with async-await.
const dbPromise = await openDB('example-database', version, events);
}
useDB();
Questo metodo restituisce una promessa che si risolve in un oggetto del database. Quando utilizzi il metodo openDB()
, fornisci un nome, un numero di versione e un oggetto eventi per configurare il database.
Ecco un esempio del metodo openDB()
nel contesto:
import {openDB} from 'idb';
async function useDB () {
// Opens the first version of the 'test-db1' database.
// If the database does not exist, it will be created.
const dbPromise = await openDB('test-db1', 1);
}
useDB();
Seleziona il controllo del supporto IndexedDB nella parte superiore della funzione anonima. In questo modo esce dalla funzione se il browser non supporta IndexedDB. Se la funzione può continuare, chiama il metodo openDB()
per aprire un database denominato 'test-db1'
.
In questo esempio, l'oggetto eventi facoltativo è stato tralasciato per semplificare le cose, ma devi specificarlo per svolgere qualsiasi lavoro significativo con IndexedDB.
Come utilizzare gli archivi di oggetti
Un database IndexedDB contiene uno o più archivi di oggetti, ciascuno con una colonna per una chiave e un'altra colonna per i dati associati a quella chiave.
Creazione di archivi di oggetti
Un database IndexedDB ben strutturato deve avere un archivio di oggetti per ogni tipo di dati che deve essere mantenuto. Ad esempio, un sito che memorizza profili
utente e note potrebbe avere un archivio di oggetti people
contenente person
oggetti e un archivio di oggetti notes
contenente note
oggetti.
Per garantire l'integrità del database, puoi creare o rimuovere archivi di oggetti solo nell'oggetto Eventi in una chiamata openDB()
. L'oggetto event espone un metodo upgrade()
che consente di creare archivi di oggetti. Chiama il metodo
createObjectStore()
all'interno del metodo upgrade()
per creare l'archivio di oggetti:
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('example-database', 1, {
upgrade (db) {
// Creates an object store:
db.createObjectStore('storeName', options);
}
});
}
createStoreInDB();
Questo metodo prende il nome dell'archivio di oggetti e un oggetto di configurazione facoltativo che consente di definire varie proprietà per l'archivio di oggetti.
Di seguito è riportato un esempio di come utilizzare createObjectStore()
:
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('test-db1', 1, {
upgrade (db) {
console.log('Creating a new object store...');
// Checks if the object store exists:
if (!db.objectStoreNames.contains('people')) {
// If the object store does not exist, create it:
db.createObjectStore('people');
}
}
});
}
createStoreInDB();
In questo esempio, un oggetto eventi viene passato al metodo openDB()
per creare l'archivio di oggetti e, come prima, il lavoro di creazione dell'archivio di oggetti viene eseguito nel metodo upgrade()
dell'oggetto evento. Tuttavia, poiché il browser genera un errore se provi a creare un archivio di oggetti già esistente, ti consigliamo di eseguire il wrapping del metodo createObjectStore()
in un'istruzione if
che verifica se l'archivio di oggetti esiste. All'interno del blocco if
, chiama createObjectStore()
per creare un archivio di oggetti denominato 'firstOS'
.
Come definire le chiavi primarie
Quando definisci gli archivi di oggetti, puoi definire in che modo i dati vengono identificati in modo univoco nel datastore utilizzando una chiave primaria. Puoi definire una chiave primaria definendo un percorso chiave o utilizzando un generatore di chiavi.
Un percorso chiave è una proprietà sempre esistente e contenente un valore univoco. Ad esempio, nel caso di un archivio di oggetti people
, potresti scegliere l'indirizzo email come percorso chiave:
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('test-db2', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('people')) {
db.createObjectStore('people', { keyPath: 'email' });
}
}
});
}
createStoreInDB();
Questo esempio crea un archivio di oggetti denominato 'people'
e assegna la proprietà email
come chiave primaria nell'opzione keyPath
.
Puoi anche utilizzare un generatore di chiavi come autoIncrement
. Il generatore di chiavi crea un valore univoco per ogni oggetto aggiunto all'archivio di oggetti. Per impostazione predefinita, se non specifichi una chiave, IndexedDB crea una chiave e la archivia separatamente dai dati.
L'esempio seguente crea un archivio di oggetti denominato 'notes'
e imposta
la chiave primaria da assegnare automaticamente come numero con incremento automatico:
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('test-db2', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('notes')) {
db.createObjectStore('notes', { autoIncrement: true });
}
}
});
}
createStoreInDB();
L'esempio seguente è simile all'esempio precedente, ma questa volta il valore con incremento automatico viene assegnato esplicitamente a una proprietà denominata 'id'
.
import {openDB} from 'idb';
async function createStoreInDB () {
const dbPromise = await openDB('test-db2', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('logs')) {
db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
}
}
});
}
createStoreInDB();
La scelta del metodo da utilizzare per definire la chiave dipende dai dati. Se i tuoi dati hanno una proprietà sempre univoca, puoi impostarla come keyPath
per applicare questa univocità. Altrimenti, utilizza un valore che incrementa automaticamente.
Il codice seguente crea tre archivi di oggetti che mostrano i vari modi di definire le chiavi primarie negli archivi di oggetti:
import {openDB} from 'idb';
async function createStoresInDB () {
const dbPromise = await openDB('test-db2', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('people')) {
db.createObjectStore('people', { keyPath: 'email' });
}
if (!db.objectStoreNames.contains('notes')) {
db.createObjectStore('notes', { autoIncrement: true });
}
if (!db.objectStoreNames.contains('logs')) {
db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
}
}
});
}
createStoresInDB();
Come definire gli indici
Gli indici sono un tipo di archivio di oggetti utilizzato per recuperare dati dall'archivio di oggetti di riferimento da parte di una proprietà specificata. Un indice si trova all'interno dell'archivio degli oggetti di riferimento e contiene gli stessi dati, ma utilizza la proprietà specificata come percorso chiave anziché la chiave primaria dell'archivio di riferimento. Gli indici devono essere creati durante la creazione degli archivi di oggetti e possono essere utilizzati per definire un vincolo univoco sui dati.
Per creare un indice, chiama il metodo createIndex()
su un'istanza dell'archivio di oggetti:
import {openDB} from 'idb';
async function createIndexInStore() {
const dbPromise = await openDB('storeName', 1, {
upgrade (db) {
const objectStore = db.createObjectStore('storeName');
objectStore.createIndex('indexName', 'property', options);
}
});
}
createIndexInStore();
Questo metodo crea e restituisce un oggetto indice. Il metodo createIndex()
nell'istanza dell'archivio di oggetti prende il nome del nuovo indice come primo argomento, mentre il secondo argomento si riferisce alla proprietà sui dati che vuoi indicizzare. L'argomento finale consente di definire due opzioni che determinano il funzionamento dell'indice: unique
e multiEntry
. Se unique
è impostato su true
, l'indice non consente valori duplicati per una singola chiave. Poi, multiEntry
determina il comportamento di createIndex()
quando la proprietà indicizzata è un array. Se
è impostato su true
, createIndex()
aggiunge una voce nell'indice
per ogni elemento dell'array. In caso contrario, viene aggiunta una singola voce contenente l'array.
Esempio:
import {openDB} from 'idb';
async function createIndexesInStores () {
const dbPromise = await openDB('test-db3', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('people')) {
const peopleObjectStore = db.createObjectStore('people', { keyPath: 'email' });
peopleObjectStore.createIndex('gender', 'gender', { unique: false });
peopleObjectStore.createIndex('ssn', 'ssn', { unique: true });
}
if (!db.objectStoreNames.contains('notes')) {
const notesObjectStore = db.createObjectStore('notes', { autoIncrement: true });
notesObjectStore.createIndex('title', 'title', { unique: false });
}
if (!db.objectStoreNames.contains('logs')) {
const logsObjectStore = db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
}
}
});
}
createIndexesInStores();
In questo esempio, gli archivi di oggetti 'people'
e 'notes'
hanno indici. Per creare gli indici, assegna prima il risultato di createObjectStore()
(un oggetto dell'archivio oggetti) a una variabile in modo da poter chiamare createIndex()
.
Come utilizzare i dati
Questa sezione descrive come creare, leggere, aggiornare ed eliminare i dati. Queste operazioni sono tutte asincrone, utilizzando promesse in cui l'API IndexedDB utilizza le richieste. Ciò semplifica l'API. Anziché ascoltare gli eventi attivati dalla richiesta, puoi chiamare .then()
sull'oggetto di database restituito dal metodo openDB()
per avviare le interazioni con il database o await
la sua creazione.
Tutte le operazioni sui dati in IndexedDB vengono eseguite all'interno di una transazione. Ogni operazione ha il seguente formato:
- Recupera oggetto di database.
- Apri transazione nel database.
- Apri l'archivio di oggetti al momento della transazione.
- Esegui un'operazione sull'archivio di oggetti.
Una transazione può essere considerata come un wrapper sicuro per un'operazione o un gruppo di operazioni. Se una delle azioni in una transazione ha esito negativo, viene eseguito il rollback di tutte le azioni. Le transazioni sono specifiche di uno o più archivi di oggetti, che definisci quando apri la transazione. Possono essere di sola lettura o di lettura e scrittura. Questo indica se le operazioni all'interno della transazione leggono i dati o apportano una modifica al database.
Creare i dati
Per creare i dati, chiama il metodo add()
sull'istanza di database e trasmetti i dati che vuoi aggiungere. Il primo argomento del metodo add()
è l'archivio di oggetti a cui vuoi aggiungere i dati e il secondo argomento è un oggetto che contiene i campi e i dati associati che vuoi aggiungere. Ecco l'esempio più semplice, in cui viene aggiunta una singola riga di dati:
import {openDB} from 'idb';
async function addItemToStore () {
const db = await openDB('example-database', 1);
await db.add('storeName', {
field: 'data'
});
}
addItemToStore();
Ogni chiamata a add()
avviene all'interno di una transazione, quindi, anche se la promessa si risolve
in modo corretto, non significa necessariamente che l'operazione abbia funzionato. Per assicurarti che l'operazione di aggiunta sia stata eseguita, devi verificare se l'intera transazione è stata completata utilizzando il metodo transaction.done()
. Si tratta di una promessa che si risolve quando la transazione viene completata da sola e, in caso di errori, viene rifiutata. Devi eseguire questo controllo per tutte le operazioni di "scrittura",
poiché è l'unico modo per sapere che le modifiche al database sono
effettivamente avvenute.
Il seguente codice mostra l'utilizzo del metodo add()
in una transazione:
import {openDB} from 'idb';
async function addItemsToStore () {
const db = await openDB('test-db4', 1, {
upgrade (db) {
if (!db.objectStoreNames.contains('foods')) {
db.createObjectStore('foods', { keyPath: 'name' });
}
}
});
// Create a transaction on the 'foods' store in read/write mode:
const tx = db.transaction('foods', 'readwrite');
// Add multiple items to the 'foods' store in a single transaction:
await Promise.all([
tx.store.add({
name: 'Sandwich',
price: 4.99,
description: 'A very tasty sandwich!',
created: new Date().getTime(),
}),
tx.store.add({
name: 'Eggs',
price: 2.99,
description: 'Some nice eggs you can cook up!',
created: new Date().getTime(),
}),
tx.done
]);
}
addItemsToStore();
Una volta aperto il database (e creato un archivio di oggetti, se necessario), devi aprire una transazione chiamando il metodo transaction()
sul database. Questo metodo prende un argomento per il negozio su cui vuoi eseguire le transazioni, nonché per la modalità.
In questo caso ci interessa scrivere allo store, quindi questo esempio specifica 'readwrite'
.
Il passaggio successivo consiste nell'iniziare ad aggiungere articoli al negozio come parte della transazione.
Nell'esempio precedente, abbiamo a che fare con tre operazioni sullo store 'foods'
che restituiscono ciascuna una promessa:
- Aggiungo un record per un delizioso panino.
- Aggiunta di un record per alcune uova.
- Segnale che la transazione è stata completata (
tx.done
).
Poiché tutte queste azioni sono basate su promesse, dobbiamo attendere che siano completate. Trasmettere queste promesse a
Promise.all
è un modo piacevole ed ergonomico per raggiungere questo obiettivo. Promise.all
accetta un array di promesse e termina una volta che tutte le promesse sono state risolte.
Per i due record che vengono aggiunti, l'interfaccia store
dell'istanza di transazione chiama add()
e vi passa i dati. Puoi await
la chiamata Promise.all
in modo che finisca al termine della transazione.
Lettura di dati
Per leggere i dati, chiama il metodo get()
sull'istanza di database che recupera usando il metodo openDB()
.
get()
prende il nome dell'archivio e il valore della chiave primaria dell'oggetto che
vuoi recuperare. Ecco un esempio di base:
import {openDB} from 'idb';
async function getItemFromStore () {
const db = await openDB('example-database', 1);
// Get a value from the object store by its primary key value:
const value = await db.get('storeName', 'unique-primary-key-value');
}
getItemFromStore();
Come per add()
, il metodo get()
restituisce una promessa, quindi puoi await
, se preferisci, o utilizzare il callback .then()
della promessa.
L'esempio seguente utilizza il metodo get()
nell'archivio di oggetti 'foods'
del database 'test-db4'
per ottenere una singola riga tramite la chiave primaria 'name'
:
import {openDB} from 'idb';
async function getItemFromStore () {
const db = await openDB('test-db4', 1);
const value = await db.get('foods', 'Sandwich');
console.dir(value);
}
getItemFromStore();
Il recupero di una singola riga dal database è piuttosto semplice: apri il database e specifica l'archivio oggetti e il valore della chiave primaria della riga da cui vuoi recuperare i dati. Poiché il metodo get()
restituisce una promessa, puoi await
questo metodo.
Aggiorna dati
Per aggiornare i dati, chiama il metodo put()
nell'archivio oggetti. Il metodo put()
è simile al metodo add()
e può essere utilizzato anche al posto di add()
per creare dati. Ecco un esempio di base dell'utilizzo di put()
per aggiornare una riga in un archivio di oggetti in base al valore della chiave primaria:
import {openDB} from 'idb';
async function updateItemInStore () {
const db = await openDB('example-database', 1);
// Update a value from in an object store with an inline key:
await db.put('storeName', { inlineKeyName: 'newValue' });
// Update a value from in an object store with an out-of-line key.
// In this case, the out-of-line key value is 1, which is the
// auto-incremented value.
await db.put('otherStoreName', { field: 'value' }, 1);
}
updateItemInStore();
Come altri metodi, questo metodo restituisce una promessa. Puoi anche utilizzare put()
nell'ambito di una transazione. Ecco un esempio utilizzando il negozio 'foods'
di prima
che aggiorna il prezzo del panino e delle uova:
import {openDB} from 'idb';
async function updateItemsInStore () {
const db = await openDB('test-db4', 1);
// Create a transaction on the 'foods' store in read/write mode:
const tx = db.transaction('foods', 'readwrite');
// Update multiple items in the 'foods' store in a single transaction:
await Promise.all([
tx.store.put({
name: 'Sandwich',
price: 5.99,
description: 'A MORE tasty sandwich!',
updated: new Date().getTime() // This creates a new field
}),
tx.store.put({
name: 'Eggs',
price: 3.99,
description: 'Some even NICER eggs you can cook up!',
updated: new Date().getTime() // This creates a new field
}),
tx.done
]);
}
updateItemsInStore();
La modalità di aggiornamento degli elementi dipende da come imposti una chiave. Se imposti un valore keyPath
, ogni riga nell'archivio di oggetti è associata a una chiave in linea. L'esempio precedente aggiorna le righe in base a questa chiave e, quando aggiorni le righe in questa situazione, devi specificare la chiave per aggiornare l'elemento appropriato nell'archivio oggetti. Puoi anche creare una chiave fuori linea impostando una chiave autoIncrement
come chiave primaria.
Elimina i dati
Per eliminare i dati, chiama il metodo delete()
nell'archivio oggetti:
import {openDB} from 'idb';
async function deleteItemFromStore () {
const db = await openDB('example-database', 1);
// Delete a value
await db.delete('storeName', 'primary-key-value');
}
deleteItemFromStore();
Come add()
e put()
, puoi utilizzare queste informazioni come parte di una transazione:
import {openDB} from 'idb';
async function deleteItemsFromStore () {
const db = await openDB('test-db4', 1);
// Create a transaction on the 'foods' store in read/write mode:
const tx = db.transaction('foods', 'readwrite');
// Delete multiple items from the 'foods' store in a single transaction:
await Promise.all([
tx.store.delete('Sandwich'),
tx.store.delete('Eggs'),
tx.done
]);
}
deleteItemsFromStore();
La struttura dell'interazione con il database è la stessa delle altre operazioni. Ricordati di verificare che l'intera transazione sia stata completata includendo il metodo tx.done
nell'array che passi a Promise.all
.
Recupero di tutti i dati
Finora hai recuperato solo gli oggetti dall'archivio uno alla volta. Puoi anche recuperare tutti i dati, o un sottoinsieme, da un archivio o indice di oggetti utilizzando il metodo getAll()
o i cursori.
Il metodo getAll()
Il modo più semplice per recuperare tutti i dati di un archivio di oggetti è chiamare getAll()
nell'archivio o sull'indice di oggetti, in questo modo:
import {openDB} from 'idb';
async function getAllItemsFromStore () {
const db = await openDB('test-db4', 1);
// Get all values from the designated object store:
const allValues = await db.getAll('storeName');
console.dir(allValues);
}
getAllItemsFromStore();
Questo metodo restituisce tutti gli oggetti nell'archivio di oggetti, senza vincoli. È il modo più diretto per ottenere tutti i valori da un archivio di oggetti, ma anche il meno flessibile.
import {openDB} from 'idb';
async function getAllItemsFromStore () {
const db = await openDB('test-db4', 1);
// Get all values from the designated object store:
const allValues = await db.getAll('foods');
console.dir(allValues);
}
getAllItemsFromStore();
Questo esempio chiama getAll()
nell'archivio di oggetti 'foods'
. Vengono restituiti tutti gli oggetti da 'foods'
, ordinati in base alla chiave primaria.
Come usare i cursori
I cursori consentono di recuperare più oggetti in modo più flessibile. Un cursore seleziona singolarmente ogni oggetto in un archivio o indice di oggetti, consentendoti di utilizzare i dati quando vengono selezionati. I cursori, come le altre operazioni del database, funzionano nelle transazioni.
Per creare un cursore, chiama openCursor()
nell'archivio oggetti come parte di una transazione. Utilizzando l'archivio 'foods'
degli esempi precedenti, ecco come avanzare con il cursore tra tutte le righe di dati in un archivio di oggetti:
import {openDB} from 'idb';
async function getAllItemsFromStoreWithCursor () {
const db = await openDB('test-db4', 1);
const tx = await db.transaction('foods', 'readonly');
// Open a cursor on the designated object store:
let cursor = await tx.store.openCursor();
// Iterate on the cursor, row by row:
while (cursor) {
// Show the data in the row at the current cursor position:
console.log(cursor.key, cursor.value);
// Advance the cursor to the next row:
cursor = await cursor.continue();
}
}
getAllItemsFromStoreWithCursor();
La transazione in questo caso viene aperta in modalità 'readonly'
e viene richiamato il relativo metodo openCursor
. In un ciclo while
successivo, la riga nella posizione attuale del cursore può ricevere le proprietà key
e value
e puoi operare su quei valori nel modo più adatto alla tua app. Quando è tutto pronto, puoi chiamare il metodo continue()
dell'oggetto cursor
per passare alla riga successiva e il loop while
termina quando il cursore raggiunge la fine del set di dati.
Utilizzare i cursori con intervalli e indici
Gli indici consentono di recuperare i dati in un archivio di oggetti da una proprietà diversa dalla chiave primaria. Puoi creare un indice su qualsiasi proprietà, che diventa keyPath
per l'indice, specificare un intervallo su quella proprietà e ottenere i dati all'interno dell'intervallo utilizzando getAll()
o un cursore.
Definisci l'intervallo utilizzando l'oggetto IDBKeyRange
e uno dei seguenti
metodi:
upperBound()
.lowerBound()
.bound()
(entrambi).only()
.includes()
.
I metodi upperBound()
e lowerBound()
specificano i limiti superiore e inferiore dell'intervallo.
IDBKeyRange.lowerBound(indexKey);
Oppure:
IDBKeyRange.upperBound(indexKey);
Ognuno dei due ha un argomento: il valore keyPath
dell'indice per l'elemento che vuoi specificare come limite superiore o inferiore.
Il metodo bound()
specifica un limite superiore e uno inferiore:
IDBKeyRange.bound(lowerIndexKey, upperIndexKey);
L'intervallo per queste funzioni è inclusivo per impostazione predefinita, il che significa che include i dati specificati come limiti dell'intervallo. Per escludere questi valori,
specifica l'intervallo come esclusivo passando true
come secondo argomento per
lowerBound()
o upperBound()
oppure come terzo e quarto argomento di
bound()
, rispettivamente per il limite inferiore e il limite superiore.
L'esempio successivo utilizza un indice nella proprietà 'price'
nell'archivio di oggetti 'foods'
. All'archivio ora è associato anche un modulo con due input per il limite superiore e quello inferiore dell'intervallo. Usa il seguente codice per trovare alimenti
con prezzi compresi tra questi limiti:
import {openDB} from 'idb';
async function searchItems (lower, upper) {
if (!lower === '' && upper === '') {
return;
}
let range;
if (lower !== '' && upper !== '') {
range = IDBKeyRange.bound(lower, upper);
} else if (lower === '') {
range = IDBKeyRange.upperBound(upper);
} else {
range = IDBKeyRange.lowerBound(lower);
}
const db = await openDB('test-db4', 1);
const tx = await db.transaction('foods', 'readonly');
const index = tx.store.index('price');
// Open a cursor on the designated object store:
let cursor = await index.openCursor(range);
if (!cursor) {
return;
}
// Iterate on the cursor, row by row:
while (cursor) {
// Show the data in the row at the current cursor position:
console.log(cursor.key, cursor.value);
// Advance the cursor to the next row:
cursor = await cursor.continue();
}
}
// Get items priced between one and four dollars:
searchItems(1.00, 4.00);
Il codice di esempio recupera innanzitutto i valori dei limiti e verifica se i limiti esistono. Il blocco di codice successivo determina il metodo da utilizzare per limitare l'intervallo in base ai valori. Nell'interazione con il database, apri l'archivio di oggetti nella transazione come di consueto, quindi apri l'indice 'price'
nell'archivio di oggetti. L'indice 'price'
ti consente di cercare articoli in base al prezzo.
Il codice apre un cursore sull'indice e passa all'interno dell'intervallo. Il cursore restituisce una promessa che rappresenta il primo oggetto dell'intervallo oppure undefined
se non esistono dati nell'intervallo. Il metodo cursor.continue()
restituisce un cursore che rappresenta l'oggetto successivo e continua nel loop fino a quando non raggiungi la fine dell'intervallo.
Controllo delle versioni del database
Quando chiami il metodo openDB()
, puoi specificare il numero di versione del database nel secondo parametro. In tutti gli esempi in questa guida, la versione è stata impostata su 1
, ma è possibile eseguire l'upgrade di un database a una nuova versione se devi modificarlo in qualche modo. Se la versione specificata è superiore a quella del database esistente, viene eseguito il callback upgrade
nell'oggetto evento, consentendoti di aggiungere nuovi archivi e indici di oggetti al database.
L'oggetto db
nel callback upgrade
ha una proprietà oldVersion
speciale, che indica il numero di versione del database a cui il browser ha accesso.
Puoi passare questo numero di versione in un'istruzione switch
per eseguire blocchi di codice all'interno del callback upgrade
in base al numero di versione del database esistente. Esempio:
import {openDB} from 'idb';
const db = await openDB('example-database', 2, {
upgrade (db, oldVersion) {
switch (oldVersion) {
case 0:
// Create first object store:
db.createObjectStore('store', { keyPath: 'name' });
case 1:
// Get the original object store, and create an index on it:
const tx = await db.transaction('store', 'readwrite');
tx.store.createIndex('name', 'name');
}
}
});
Questo esempio imposta la versione più recente del database su 2
. Quando questo codice viene eseguito per la prima volta, il database non esiste ancora nel browser, pertanto oldVersion
è 0
e l'istruzione switch
inizia da case 0
. Nell'esempio, viene aggiunto un archivio di oggetti 'store'
al database.
Punto chiave: nelle istruzioni switch
, di solito c'è un break
dopo ogni blocco case
, ma intenzionalmente non viene utilizzato qui. In questo modo, se il database esistente è indietro di alcune versioni o non esiste, il codice continua per il resto dei blocchi case
fino a quando non è aggiornato. Nell'esempio, quindi, il browser continua a eseguire tramite case 1
, creando un indice name
nell'archivio di oggetti store
.
Per creare un indice 'description'
nell'archivio di oggetti 'store'
, aggiorna il numero di versione e aggiungi un nuovo blocco case
come segue:
import {openDB} from 'idb';
const db = await openDB('example-database', 3, {
upgrade (db, oldVersion) {
switch (oldVersion) {
case 0:
// Create first object store:
db.createObjectStore('store', { keyPath: 'name' });
case 1:
// Get the original object store, and create an index on it:
const tx = await db.transaction('store', 'readwrite');
tx.store.createIndex('name', 'name');
case 2:
const tx = await db.transaction('store', 'readwrite');
tx.store.createIndex('description', 'description');
}
}
});
Se il database creato nell'esempio precedente esiste ancora nel browser, quando viene eseguito, oldVersion
è 2
. Il browser ignora case 0
e
case 1
ed esegue il codice in case 2
, creando un indice description
. Dopodiché, il browser dispone di un database versione 3 contenente un archivio di oggetti store
con indici name
e description
.
Per approfondire
Le seguenti risorse forniscono ulteriori informazioni e contesto per l'utilizzo di IndexedDB.
Documentazione di IndexedDB
idb
repository GitHub- Utilizzo di IndexedDB
- Concetti di base alla base di IndexedDB
- Specifiche dell'API Indexed Database 3.0