Una guida rapida ma completa a IndexedDB e alla memorizzazione dei dati nei browser

Ti interessa imparare JavaScript? Ottieni il mio ebook gratuito su jshandbook.com

IndexedDB è una delle funzionalità di archiviazione introdotte nei browser nel corso degli anni. È un archivio chiave / valore (un database noSQL) considerato la soluzione definitiva per l'archiviazione dei dati nei browser.

IndexedDB è un'API asincrona, il che significa che eseguire operazioni costose non bloccherà il thread dell'interfaccia utente fornendo un'esperienza sciatta agli utenti.

Può archiviare una quantità indefinita di dati, anche se una volta superata una determinata soglia viene richiesto all'utente di assegnare limiti più elevati al sito.

È supportato su tutti i browser moderni.

Supporta transazioni e versioning e offre buone prestazioni.

All'interno del browser possiamo anche usare:

  • Cookie, che possono ospitare una quantità molto piccola di stringhe
  • DOM Storage (o Web Storage), un termine che identifica comunemente localStorage e sessionStorage, due archivi chiave / valore. sessionStorage, non conserva i dati, che vengono cancellati al termine della sessione, mentre localStorage mantiene i dati tra le sessioni

Lo spazio di archiviazione locale / sessione ha lo svantaggio di essere limitato a dimensioni ridotte (e incoerenti), con l'implementazione dei browser che offre da 2 MB a 10 MB di spazio per sito.

In passato avevamo anche Web SQL, un wrapper per SQLite. Ma questo è ora deprecato e non supportato su alcuni browser moderni. Non è mai stato uno standard riconosciuto e quindi non dovrebbe essere utilizzato, sebbene l'83% degli utenti abbia questa tecnologia sui propri dispositivi secondo Can I Use.

Sebbene sia possibile creare tecnicamente più database per sito, in genere si crea un singolo database. All'interno di quel database è possibile creare più archivi oggetti.

Un database è privato di un dominio, quindi qualsiasi altro sito non può accedere ai negozi IndexedDB di un altro sito Web.

Ogni negozio di solito contiene un insieme di cose, che possono essere:

  • stringhe
  • numeri
  • oggetti
  • array
  • date

Ad esempio, potresti avere un negozio che contiene post e un altro che contiene commenti.

Un negozio contiene un numero di articoli che hanno una chiave univoca, che rappresenta il modo in cui un oggetto può essere identificato.

È possibile modificare tali negozi utilizzando le transazioni, eseguendo operazioni di aggiunta, modifica ed eliminazione e ripetendo gli articoli in essi contenuti.

Dall'avvento di Promises in ES2015 e il successivo passaggio delle API all'utilizzo delle promesse, l'API IndexedDB sembra un po 'vecchia scuola.

Anche se non c'è nulla di sbagliato in questo, in tutti gli esempi che spiego userò la libreria promessa IndexedDB di Jake Archibald, che è un piccolo strato sopra l'API IndexedDB per renderlo più facile da usare.

Questa libreria è utilizzata anche in tutti gli esempi sul sito Web di Google Developers relativi a IndexedDB.

Creare un database IndexedDB

Includi la lib idb usando:

filato aggiungere idb

E poi includilo nella tua pagina, usando Webpack o Browserify o qualsiasi altro sistema di compilazione, o semplicemente:

E siamo pronti per partire.

Prima di utilizzare l'API IndexedDB, assicurati sempre di controllare il supporto nel browser, anche se è ampiamente disponibile. Non si sa mai quale browser sta utilizzando l'utente:

(() => {
  'usa rigoroso'
  if (! ('indexedDB' nella finestra)) {
    console.warn ('IndexedDB non supportato')
    ritorno
  }
//...IndexedDB code
}) ()

Come creare un database

Utilizzando idb.open ():

const name = 'mydbname'
const version = 1 // le versioni iniziano da 1
idb.open (nome, versione, upgradeDb => {})

I primi due parametri sono autoesplicativi. Il terzo parametro, che è facoltativo, è un callback chiamato solo se il numero di versione è superiore alla versione corrente del database installato. Nel corpo della funzione di callback è possibile aggiornare la struttura (archivi e indici) del db.

Usiamo il nome upgradeDB per il callback per identificare che questo è il momento di aggiornare il database, se necessario.

Crea un archivio oggetti

Come creare un archivio oggetti o aggiungerne uno nuovo

Un archivio oggetti viene creato o aggiornato in questo callback, utilizzando la sintassi db.createObjectStore ('storeName', opzioni):

const dbPromise = idb.open ('mydb', 1, (upgradeDB) => {
  upgradeDB.createObjectStore ( 'store1')
})
.then (db => console.log ('success'))

Se hai installato una versione precedente, il callback ti consente di eseguire la migrazione:

const dbPromise = idb.open ('keyval-store', 3, (upgradeDB) => {
  switch (upgradeDB.oldVersion) {
    case 0: // nessun db creato prima
      // un negozio introdotto nella versione 1
      upgradeDB.createObjectStore ( 'store1')
    caso 1:
      // un nuovo negozio nella versione 2
      upgradeDB.createObjectStore ('store2', {keyPath: 'name'})
  }
})
.then (db => console.log ('success'))

createObjectStore () come puoi vedere nel caso 1 accetta un secondo parametro che indica la chiave di indice del database. Ciò è molto utile quando si memorizzano oggetti: le chiamate put () non richiedono un secondo parametro, ma possono semplicemente assumere il valore (un oggetto) e la chiave verrà mappata sulla proprietà dell'oggetto che ha quel nome.

L'indice offre un modo per recuperare un valore in seguito tramite quella chiave specifica e deve essere univoco (ogni elemento deve avere una chiave diversa).

È possibile impostare una chiave per l'incremento automatico, quindi non è necessario tenerne traccia sul codice client. Se non specifichi una chiave, IndexedDB la creerà in modo trasparente per noi:

upgradeDb.createObjectStore ('notes', {autoIncrement: true})

ma puoi anche specificare un campo specifico del valore dell'oggetto da incrementare automaticamente:

upgradeDb.createObjectStore ('notes', {
  keyPath: 'id',
  autoIncrement: true
})

Come regola generale, utilizzare l'incremento automatico se i valori non contengono già una chiave univoca (ad esempio, un indirizzo e-mail per gli utenti).

indici

Un indice è un modo per recuperare i dati dall'archivio oggetti. È definito insieme alla creazione del database nel callback idb.open () in questo modo:

const dbPromise = idb.open ('dogsdb', 1, (upgradeDB) => {
  const dogs = upgradeDB.createObjectStore ('dogs')
  dogs.createIndex ('name', 'name', {unique: false})
})

L'opzione unica determina se il valore dell'indice deve essere univoco e non è possibile aggiungere valori duplicati.

È possibile accedere a un archivio oggetti già creato utilizzando il metodo upgradeDb.transaction.objectStore ():

const dbPromise = idb.open ('dogsdb', 1, (upgradeDB) => {
  const dogs = upgradeDB.transaction.objectStore ('dogs')
  dogs.createIndex ('name', 'name', {unique: false})
})

Controlla se esiste un negozio

È possibile verificare se esiste già un archivio oggetti chiamando il metodo objectStoreNames ():

if (! upgradeDb.objectStoreNames.contains ('store3')) {
  upgradeDb.createObjectStore ( 'store3')
}

Eliminazione da IndexedDB

Elimina un database

idb.delete ('mydb'). then (() => console.log ('done'))

Elimina un archivio oggetti

Un archivio oggetti può essere eliminato solo nel callback quando si apre un db e tale callback viene chiamato solo se si specifica una versione successiva a quella attualmente installata:

const dbPromise = idb.open ('dogsdb', 2, (upgradeDB) => {
  upgradeDB.deleteObjectStore ( 'old_store')
})

Per eliminare i dati in un archivio oggetti, utilizzare questa transazione:

tasto const = 232
dbPromise.then ((db) => {
  const tx = db.transaction ('store', 'readwrite')
  const store = tx.objectStore ('store')
  store.delete (chiave)
  return tx.complete
})
.then (() => {
  console.log ('Item cancellato')
})

Aggiungi un elemento al database

Puoi usare il metodo put dell'archivio oggetti, ma prima abbiamo bisogno di un riferimento ad esso, che possiamo ottenere da upgradeDB.createObjectStore () quando lo creiamo.

Quando si utilizza put, il valore è il primo argomento e la chiave è il secondo. Questo perché se si specifica keyPath durante la creazione dell'archivio oggetti, non è necessario inserire il nome della chiave in ogni richiesta put (). Puoi semplicemente scrivere il valore.

Questo popola store0 non appena lo creiamo:

idb.open ('mydb', 1, (upgradeDB) => {
  keyValStore = upgradeDB.createObjectStore ('store0')
  keyValStore.put ('Hello world!', 'Hello')
})

Per aggiungere articoli in un secondo momento, è necessario creare una transazione. Ciò garantisce l'integrità del database (se un'operazione ha esito negativo, tutte le operazioni nella transazione vengono ripristinate e lo stato torna a uno stato noto).

Per questo, usa un riferimento all'oggetto dbPromise che abbiamo ottenuto quando chiamiamo idb.open (), ed esegui:

dbPromise.then ((db) => {
  const val = 'hey!'
  const key = 'Hello again'
  const tx = db.transaction ('store1', 'readwrite')
  tx.objectStore ('store1'). put (val, chiave)
  return tx.complete
})
.then (() => {
  console.log ('Transazione completata')
})
.catch (() => {
  console.log ('Transazione non riuscita')
})

L'API IndexedDB offre anche il metodo add (), ma poiché put () ci consente sia di aggiungere che di aggiornare, è più semplice utilizzarlo.

Ottenere articoli da un negozio

Ottenere un articolo specifico da un negozio usando get ()

dbPromise.then (db => db.transaction ('objs')
                       .objectStore ( 'objs')
                       .get (123456))
.then (obj => console.log (obj))

Ottenere tutti gli elementi usando getAll ()

dbPromise.then (db => db.transaction ('store1')
                       .objectStore ( 'store1')
                       .prendi tutto())
.then (objects => console.log (oggetti))

Iterazione su tutti gli elementi usando un cursore tramite openCursor ()

dbPromise.then ((db) => {
  const tx = db.transaction ('store', 'readonly')
  const store = tx.objectStore ('store')
  return store.openCursor ()
})
.then (funzione logItems (cursore) {
  if (! cursore) {return}
  console.log ('cursore è su:', cursore.key)
  for (campo const in cursor.value) {
    console.log (cursor.value [campo])
  }
  return cursor.continue (). then (logItems)
})
.then (() => {
  console.log ( 'fatta!')
})

Scorrere su un sottoinsieme degli elementi usando limiti e cursori

const searchItems = (lower, upper) => {
  if (lower === '' && upper === '') {return}
  lasciare intervallo
  if (lower! == '' && upper! == '') {
    range = IDBKeyRange.bound (inferiore, superiore)
  } else if (lower === '') {
    range = IDBKeyRange.upperBound (superiore)
  } altro {
    range = IDBKeyRange.lowerBound (inferiore)
  }
  dbPromise.then ((db) => {
    const tx = db.transaction (['dogs'], 'readonly')
    const store = tx.objectStore ('dogs')
    const index = store.index ('age')
    return index.openCursore (intervallo)
  })
  .then (funzione showRange (cursore) {
    if (! cursore) {return}
    console.log ('cursore è su:', cursore.key)
    for (campo const in cursor.value) {
      console.log (cursor.value [campo])
    }
    return cursor.continue (). then (showRange)
  })
  .then (() => {
    console.log ( 'fatta!')
  })
}
searchDogsBetweenAges (3, 10)
Ti interessa imparare JavaScript? Ottieni il mio ebook gratuito su jshandbook.com