使用索引資料庫

本指南說明 IndexedDB API 的基本概念。我們目前使用 Jake Archibald 的 IndexedDB Promised 程式庫,與 IndexedDB API 非常類似,但使用了承諾,您可以await獲取更簡潔的語法。這樣可簡化 API,同時維持 API 結構。

什麼是 IndexedDB?

IndexedDB 是大規模的 NoSQL 儲存系統,可儲存幾乎所有的使用者瀏覽器內容。除了一般的搜尋、取得和插入動作外,IndexedDB 還支援交易,非常適合儲存大量結構化資料。

每個 IndexedDB 資料庫都專屬於一個「來源」 (通常是網站網域或子網域),這表示其他來源無法存取或存取該資料庫。本身的資料儲存空間限制通常較大,但不同瀏覽器處理限制和資料清除的方式都不同。詳情請參閱「延伸閱讀」一節。

IndexedDB 字詞

資料庫
IndexedDB 的最高層級,這個檔案包含物件儲存空間,其中又包含您要保留的資料。您可以根據自己選擇的名稱建立多個資料庫。
物件存放區
用於儲存資料的個別值區,類似關聯資料庫中的資料表。一般來說,儲存的每個類型 (非 JavaScript 資料類型) 都會擁有一個物件儲存空間。與資料庫資料表不同的是,儲存庫中的 JavaScript 資料類型不需要保持一致。舉例來說,如果應用程式的 people 物件存放區包含三人的相關資訊,這些使用者的年齡屬性可能是 53'twenty-five'unknown
索引
一種物件存放區,可透過資料的個別屬性整理其他物件存放區 (稱為參照物件存放區) 中的資料。索引可用來擷取這個屬性在物件存放區中的記錄。例如,如果您是養護人員,可能會想稍後再透過姓名、年齡或喜愛的動物來擷取他們。
作業
與資料庫的互動。
交易
單一作業或一組作業的包裝函式,確保資料庫完整性。如果交易中的其中一個動作失敗,則不會套用這些動作,資料庫也會回到交易發生之前的狀態。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();

這個方法會傳回一個承諾,該承諾會解析為資料庫物件。使用 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,這麼做會結束函式。如果函式可以繼續,就會呼叫 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' 的物件存放區,並在 keyPath 選項中指派 email 屬性做為主鍵。

您也可以使用金鑰產生器,例如 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() 方法會使用新索引的名稱做為第一個引數,第二個引數則參照要建立索引的資料屬性。最後一個引數可讓您定義兩個選項,以決定索引的運作方式:uniquemultiEntry。如果 unique 設為 true,則索引不允許單一索引鍵使用重複的值。接下來,multiEntry 會決定 createIndex() 在已建立索引的屬性是陣列時的行為。如果設為 truecreateIndex() 會針對每個陣列元素在索引中加入一個項目。否則,會新增一個包含陣列的項目。

範例如下:

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 提出要求時就會進行承諾。這樣可以簡化 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() 呼叫都會在交易中進行,因此即使承諾可以成功解決,也不一定代表作業有效。如要確認新增作業執行,您必須使用 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' 儲存庫中的三項作業,這些作業每次都會傳回一個承諾:

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

由於這些行動都是基於承諾的,我們需要等待所有這些動作完成。將這些承諾傳遞給 Promise.all 是很好、符合人體工學的方式,可達到做到這點。Promise.all 會接受一系列承諾,並在傳遞的所有承諾項目中完成時結束。

對於要新增的兩筆記錄,交易執行個體的 store 介面會呼叫 add(),並將資料傳送至該記錄。您可以將 Promise.all 呼叫設為 await,這樣就會在交易完成時完成。

讀取資料

如要讀取資料,請在使用 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() 方法會傳回一個承諾,因此您可以視需要 await 使用該項目,或使用承諾的 .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() 方法會傳回承諾,因此您可以 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 迴圈中,遊標目前位置的資料列可能會讀取其 keyvalue 屬性,並以最適合應用程式的方式處理這些值。準備就緒時,您可以呼叫 cursor 物件的 continue() 方法前往下一個資料列,當遊標到達資料集結尾時,while 迴圈就會終止。

使用含有範圍和索引的遊標

索引可讓您依據主鍵以外的屬性,擷取物件存放區中的資料。您可以在任何屬性上建立索引,該屬性會成為索引的 keyPath,然後指定該屬性的範圍,然後使用 getAll() 或遊標取得該範圍中的資料。

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

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

IDBKeyRange.lowerBound(indexKey);

或:

IDBKeyRange.upperBound(indexKey);

每個引數都會使用一個引數:您要指定為上限或下限的項目之索引的 keyPath 值。

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

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);

根據預設,這些函式的範圍含括在內,也就是包含指定做為範圍限制的資料。如要排除這些值,請將 true 做為 lowerBound()upperBound() 的第二個引數傳遞 true,或是分別做為 bound() 的第三和第四個引數,分別指定範圍為「專屬」範圍。

下一個範例使用 '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' 索引可讓您依價格搜尋項目。

程式碼隨即會在索引上開啟遊標,並傳入範圍。遊標會傳回代表範圍內第一個物件的承諾,或者如果範圍內沒有資料,則傳回 undefinedcursor.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。初次執行這段程式碼時,瀏覽器中尚未存在資料庫,因此 oldVersion0,而 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');
    }
  }
});

如果您在上一個範例中建立的資料庫仍存在於瀏覽器中,則執行時,oldVersion2。瀏覽器會略過 case 0case 1,並在 case 2 中執行程式碼,進而建立 description 索引。之後,瀏覽器就會擁有第 3 版的資料庫,其中包含包含 namedescription 索引的 store 物件存放區。

其他資訊

下列資源提供了使用 IndexedDB 的詳細資訊和背景資訊。

IndexedDB 說明文件

資料儲存限制