使用索引資料庫

本指南涵蓋 IndexedDB API 的基本概念。我們使用的是 Jake Archibald 的 IndexedDB Promised 程式庫,這與 IndexedDB API 非常相似,但使用 Promise,您可以 await 取得更簡潔的語法。這可簡化 API,同時維持其結構。

什麼是 IndexedDB?

IndexedDB 是大型 NoSQL 儲存系統,幾乎可以儲存使用者瀏覽器中的任何內容。除了常見的搜尋、取得和放置動作外,IndexedDB 也支援交易,非常適合儲存大量結構化資料。

每個 IndexedDB 資料庫都專屬於一個來源 (通常是網站網域或子網域),也就是說,它無法存取其他來源,也無法被其他來源存取。資料儲存空間限制通常很大 (如果有的話),但不同瀏覽器處理限制和資料淘汰的方式不同。詳情請參閱「延伸閱讀」一節。

IndexedDB 條款

資料庫
IndexedDB 的最高層級。其中包含物件儲存庫,而物件儲存庫則包含要保留的資料。您可以建立多個資料庫,並自行命名。
物件儲存庫
用於儲存資料的個別值區,類似關聯式資料庫中的資料表。 一般來說,每種類型 (非 JavaScript 資料類型) 的資料都有一個物件儲存庫。與資料庫資料表不同,儲存庫中的 JavaScript 資料類型不一定要一致。舉例來說,如果應用程式有一個 people 物件儲存庫,其中包含三個人的資訊,則這些人的年齡屬性可能是 53、'twenty-five' 和 unknown。
指數
一種物件儲存庫,可依資料的個別屬性,整理另一個物件儲存庫 (稱為參照物件儲存庫) 中的資料。索引會用於透過這個屬性擷取物件儲存庫中的記錄。舉例來說,如果您要儲存使用者,之後可能會想依名稱、年齡或喜愛的動物擷取使用者。
作業
與資料庫互動。
交易
作業或作業群組的包裝函式,可確保資料庫完整性。如果交易中的其中一項動作失敗,則所有動作都不會套用,且資料庫會返回交易開始前的狀態。IndexedDB 中的所有讀取或寫入作業都必須是交易的一部分。這樣一來,您就能執行不可分割的讀取-修改-寫入作業,不必擔心與同時在資料庫上執行的其他執行緒發生衝突。
Cursor
:用於疊代資料庫中多筆記錄的機制。

如何檢查 IndexedDB 支援

IndexedDB 幾乎全面支援。不過,如果您使用的是舊版瀏覽器,建議您偵測功能支援,以防萬一。最簡單的方法是檢查 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();

如何開啟資料庫

您可以使用 IndexedDB 建立多個資料庫,並自行命名。如果嘗試開啟的資料庫不存在,系統會自動建立。如要開啟資料庫,請使用 idb 程式庫中的 openDB() 方法:

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();

這個方法會傳回一個 promise,解析為資料庫物件。使用 openDB() 方法時,請提供名稱、版本號碼和事件物件,以設定資料庫。

以下是 openDB() 方法的範例:

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();

在匿名函式頂端,加入 IndexedDB 支援檢查。如果瀏覽器不支援 IndexedDB,這會結束函式。如果函式可以繼續,就會呼叫 openDB() 方法,開啟名為 'test-db1' 的資料庫。在這個範例中,我們省略了選用的事件物件,以簡化內容,但您必須指定該物件,才能使用 IndexedDB 執行任何有意義的工作。

如何使用物件儲存庫

IndexedDB 資料庫包含一或多個物件儲存庫,每個物件儲存庫都有一個鍵欄,以及另一個與該鍵相關聯的資料欄。

建立物件儲存庫

結構良好的 IndexedDB 資料庫,應為每種需要保留的資料類型建立一個物件儲存庫。舉例來說,如果網站會保留使用者個人資料和記事,可能會有包含 person 物件的 people 物件儲存庫,以及包含 note 物件的 notes 物件儲存庫。

為確保資料庫完整性,您只能在 openDB() 呼叫的事件物件中建立或移除物件儲存庫。事件物件會公開 upgrade() 方法,讓您建立物件儲存庫。在 upgrade() 方法中呼叫 createObjectStore() 方法,建立物件儲存庫:

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();

這個方法會採用物件儲存庫的名稱,以及選用的設定物件,讓您定義物件儲存庫的各種屬性。

以下是 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();

在這個範例中,事件物件會傳遞至 openDB() 方法來建立物件儲存庫,而建立物件儲存庫的工作則是在事件物件的 upgrade() 方法中完成。不過,如果您嘗試建立已存在的物件儲存庫,瀏覽器會擲回錯誤,因此建議您將 createObjectStore() 方法包裝在 if 陳述式中,檢查物件儲存庫是否存在。在 if 區塊中,呼叫 createObjectStore() 建立名為 'firstOS' 的物件儲存庫。

如何定義主鍵

定義物件儲存庫時,您可以使用主鍵,定義如何在儲存庫中唯一識別資料。您可以定義索引鍵路徑或使用索引鍵產生器,來定義主鍵。

鍵路徑是必定存在且包含不重複值的屬性。舉例來說,如果是 people 物件儲存庫,您可以選擇電子郵件地址做為索引鍵路徑:

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();

這個範例會建立名為 'people' 的物件儲存庫,並將 email 屬性指派為 keyPath 選項中的主鍵。

您也可以使用 autoIncrement 等金鑰產生器。鍵產生器會為新增至物件儲存庫的每個物件建立專屬值。根據預設,如果您未指定索引鍵,IndexedDB 會建立索引鍵,並與資料分開儲存。

以下範例會建立名為 'notes' 的物件儲存庫,並將主鍵設為自動指派的遞增數字:

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();

以下範例與前一個範例類似,但這次會將自動遞增值明確指派給名為 '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();

請根據資料決定要使用哪種方法定義鍵。如果資料具有一律不重複的屬性,您可以將其設為 keyPath,強制執行這項不重複屬性。否則,請使用自動遞增值。

下列程式碼會建立三個物件儲存庫,示範在物件儲存庫中定義主鍵的多種方式:

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();

如何定義索引

索引是一種物件儲存庫,用於透過指定屬性從參照物件儲存庫擷取資料。索引位於參照物件儲存庫中,且包含相同的資料,但會使用指定屬性做為索引鍵路徑,而非參照儲存庫的主鍵。建立物件儲存庫時,必須建立索引,並可用來定義資料的專屬限制。

如要建立索引,請在物件儲存庫執行個體上呼叫 createIndex() 方法:

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();

這個方法會建立並傳回索引物件。物件儲存庫執行個體的 createIndex() 方法會將新索引的名稱做為第一個引數,第二個引數則是指要建立索引的資料屬性。最後一個引數可讓您定義兩個選項,決定索引的運作方式:unique 和 multiEntry。如果 unique 設為 true,索引就不允許單一鍵有重複的值。接下來,multiEntry 會決定當索引屬性為陣列時,createIndex() 的行為。如果設為 true,createIndex() 會在索引中為每個陣列元素新增一個項目。否則,系統會新增包含陣列的單一項目。

範例如下:

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();

在這個範例中,'people' 和 'notes' 物件儲存庫都有索引。如要建立索引,請先將 createObjectStore() (物件儲存庫物件) 的結果指派給變數,以便對其呼叫 createIndex()。

如何使用資料

本節說明如何建立、讀取、更新及刪除資料。這些作業都是非同步的,在 IndexedDB API 使用要求的地方使用 Promise。這可簡化 API。您不必監聽要求觸發的事件,只要在 openDB() 方法傳回的資料庫物件上呼叫 .then(),即可開始與資料庫互動,或await其建立。

IndexedDB 中的所有資料作業都會在交易中執行。每項作業的格式如下:

  1. 取得資料庫物件。
  2. 在資料庫上開啟交易。
  3. 在交易中開啟物件儲存庫。
  4. 對物件儲存庫執行作業。

交易可視為一項或一組作業的安全包裝函式。如果交易中的其中一項動作失敗,所有動作都會復原。交易會與一或多個物件儲存庫相關聯,您可以在開啟交易時定義這些儲存庫。可以是唯讀或讀寫。這表示交易中的作業是讀取資料,還是變更資料庫。

建立資料

如要建立資料,請在資料庫例項上呼叫 add() 方法,並傳入要新增的資料。add() 方法的第一個引數是要新增資料的物件儲存庫,第二個引數則是包含要新增欄位和相關資料的物件。以下是最簡單的範例,其中新增了一列資料:

import {openDB} from 'idb';

async function addItemToStore () {
  const db = await openDB('example-database', 1);

  await db.add('storeName', {
    field: 'data'
  });
}

addItemToStore();

每次 add() 呼叫都會在交易中發生,因此即使 Promise 成功解析,也不一定代表作業成功。為確保新增作業已執行,您需要使用 transaction.done() 方法檢查整個交易是否已完成。這項承諾會在交易完成時解析,並在交易發生錯誤時拒絕。您必須對所有「寫入」作業執行這項檢查,因為這是確認資料庫確實發生變更的唯一方法。

以下程式碼顯示在交易中使用 add() 方法:

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();

開啟資料庫 (並視需要建立物件儲存庫) 後,您必須對資料庫呼叫 transaction() 方法,開啟交易。這個方法會針對您要交易的商店和模式,採用引數。在本範例中,我們想寫入商店,因此指定 'readwrite'。

下一步是開始將商品新增至商店,做為交易的一部分。在上述範例中,我們在 'foods' 存放區中處理三項作業,每項作業都會傳回一個 Promise:

  1. 新增美味三明治的記錄。
  2. 正在新增雞蛋記錄。
  3. 表示交易已完成 (tx.done)。

由於這些動作都是以 Promise 為基礎,因此我們需要等待所有動作完成。將這些 Promise 傳遞至 Promise.all,是完成這項作業的好方法。Promise.all 接受 Promise 陣列,並在傳遞給它的所有 Promise 都解析完成後結束。

對於要新增的兩筆記錄,交易例項的 store 介面會呼叫 add(),並將資料傳遞給它。您可以await Promise.all 呼叫,讓呼叫在交易完成時結束。

讀取資料

如要讀取資料,請在透過 openDB() 方法擷取的資料庫例項上,呼叫 get() 方法。get() 會擷取商店名稱和要擷取物件的主鍵值。基本範例如下:

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();

與 add() 一樣,get() 方法會傳回 Promise,因此您可以視需要 await,或使用 Promise 的 .then() 回呼。

以下範例使用 'test-db4' 資料庫 'foods' 物件儲存庫的 get() 方法,透過 '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();

從資料庫擷取單一資料列相當簡單:開啟資料庫,並指定要從中取得資料的資料列物件儲存庫和主鍵值。由於 get() 方法會傳回 Promise,因此您可以對其執行 await。

更新資料

如要更新資料,請在物件儲存庫上呼叫 put() 方法。put() 方法與 add() 方法類似,也可以用來取代 add() 建立資料。以下是使用 put() 根據主鍵值更新物件儲存庫中資料列的基本範例:

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();

與其他方法一樣,這個方法會傳回 Promise。您也可以在交易中使用 put()。以下範例使用先前的 'foods' 商店,更新三明治和蛋的價格:

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();

項目更新方式取決於您設定鍵的方式。如果設定 keyPath,物件儲存庫中的每個資料列都會與「內嵌索引鍵」建立關聯。上述範例會根據這個索引鍵更新資料列,在這種情況下更新資料列時,您必須指定該索引鍵,才能更新物件儲存庫中的適當項目。您也可以將 autoIncrement 設為主鍵,建立非內嵌鍵。

刪除資料

如要刪除資料,請在物件儲存庫上呼叫 delete() 方法:

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();

與 add() 和 put() 一樣,您可以在交易中使用這項功能:

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();

資料庫互動的結構與其他作業相同。請記得在傳遞至 Promise.all 的陣列中加入 tx.done 方法,確認整筆交易已完成。

取得所有資料

目前為止,您一次只能從商店擷取一個物件。您也可以使用 getAll() 方法或游標,從物件儲存庫或索引擷取所有資料或子集。

getAll() 方法

如要擷取物件儲存庫的所有資料,最簡單的方法是在物件儲存庫或索引上呼叫 getAll(),如下所示:

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();

這個方法會傳回物件儲存庫中的所有物件,沒有任何限制。這是從物件儲存庫取得所有值最直接的方法,但彈性也最低。

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();

這個範例會對 'foods' 物件儲存庫呼叫 getAll()。這會傳回 'foods' 中的所有物件,並依主鍵排序。

如何使用游標

游標是擷取多個物件的更靈活方式。游標會逐一選取物件儲存庫或索引中的每個物件,讓您在選取資料時執行操作。與其他資料庫作業一樣,游標會在交易中運作。

如要建立游標,請在交易中對物件儲存庫呼叫 openCursor()。使用先前範例中的 'foods' 儲存庫,即可透過下列方式,在物件儲存庫的所有資料列中推進游標:

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();

在這種情況下,交易會以 'readonly' 模式開啟,並呼叫其 openCursor 方法。在後續的 while 迴圈中,游標目前位置的資料列可以讀取其 key 和 value 屬性,您也可以透過最適合應用程式的方式操作這些值。準備就緒後,您可以呼叫 cursor 物件的 continue() 方法前往下一個資料列,當游標到達資料集結尾時,while 迴圈就會終止。

搭配使用游標與範圍和索引

索引可讓您透過主鍵以外的屬性,擷取物件儲存庫中的資料。您可以在任何屬性上建立索引,該屬性會成為索引的 keyPath,指定該屬性的範圍,並使用 getAll() 或游標取得範圍內的資料。

使用 IDBKeyRange 物件和下列任一方法定義範圍:

upperBound() 和 lowerBound() 方法會指定範圍的上限和下限。

IDBKeyRange.lowerBound(indexKey);

或:

IDBKeyRange.upperBound(indexKey);

兩者都採用一個引數:要指定為上限或下限的項目索引 keyPath 值。

bound() 方法會同時指定上限和下限:

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);

根據預設,這些函式的範圍是包含式,也就是說,範圍會包含指定為範圍限制的資料。如要排除這些值,請將 true 做為 lowerBound() 或 upperBound() 的第二個引數,或做為 bound() 的第三和第四個引數 (分別代表下限和上限),將範圍指定為「exclusive」。

下一個範例使用 'foods' 物件儲存庫中 'price' 屬性的索引。商店現在也附加了表單,其中有兩個輸入欄位,用於設定範圍的上限和下限。請使用下列程式碼,找出價格介於這些限制之間的食物:

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);

範例程式碼會先取得限制值,並檢查限制是否存在。下一個程式碼區塊會根據值決定要使用哪種方法限制範圍。在資料庫互動中,照常開啟交易的物件儲存庫,然後開啟物件儲存庫的 'price' 索引。'price' 索引可讓您依價格搜尋商品。

接著,程式碼會在索引上開啟游標,並傳入範圍。游標會傳回代表範圍內第一個物件的 Promise,如果範圍內沒有資料,則會傳回 undefined。cursor.continue() 方法會傳回代表下一個物件的游標,並繼續執行迴圈,直到到達範圍結尾。

資料庫版本管理

呼叫 openDB() 方法時,您可以在第二個參數中指定資料庫版本號碼。在本指南的所有範例中,版本都設為 1,但如果您需要以某種方式修改資料庫,可以將資料庫升級至新版本。如果指定的版本高於現有資料庫的版本,系統會執行事件物件中的 upgrade 回呼,讓您將新的物件儲存庫和索引新增至資料庫。

upgrade 回呼中的 db 物件具有特殊的 oldVersion 屬性,可指出瀏覽器可存取的資料庫版本號碼。您可以將這個版本號碼傳遞至 switch 陳述式,根據現有的資料庫版本號碼,在 upgrade 回呼中執行程式碼區塊。範例如下:

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');
    }
  }
});

以下範例將資料庫的最新版本設為 2。首次執行這段程式碼時,瀏覽器中還沒有資料庫,因此 oldVersion 為 0,而 switch 陳述式會從 case 0 開始。在本範例中,這會將 'store' 物件儲存庫新增至資料庫。

重點:在 switch 陳述式中,每個 case 區塊後通常會有 break,但這裡刻意不使用。這樣一來,如果現有資料庫落後幾個版本,或是不存在,程式碼就會繼續執行其餘的 case 區塊,直到最新為止。因此,在範例中,瀏覽器會繼續執行 case 1,在 store 物件儲存庫上建立 name 索引。

如要在 'store' 物件儲存庫上建立 'description' 索引,請更新版本號碼,並新增 case 區塊,如下所示:

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');
    }
  }
});

如果先前範例中建立的資料庫仍存在於瀏覽器中,執行這項操作時,oldVersion 會是 2。瀏覽器會略過 case 0 和 case 1,並執行 case 2 中的程式碼,建立 description 索引。之後,瀏覽器會擁有版本 3 的資料庫,其中包含具有 name 和 description 索引的 store 物件儲存庫。

延伸閱讀

下列資源提供更多資訊和背景,協助您使用 IndexedDB。

IndexedDB 說明文件

資料儲存空間限制