Làm việc với IndexedDB

Hướng dẫn này trình bày các kiến thức cơ bản về IndexedDB API. Chúng tôi đang dùng công cụ Jake Archibald Lập chỉ mụcDB đã hứa thư viện này rất giống với IndexedDB API nhưng sử dụng các lời hứa bạn có thể await để có cú pháp ngắn gọn hơn. Điều này giúp đơn giản hoá API trong khi duy trì cấu trúc của nó.

IndexedDB là hệ thống lưu trữ NoSQL quy mô lớn cho phép chỉ lưu trữ về mọi thứ trong trình duyệt của người dùng. Ngoài cách tìm kiếm thông thường, nhận và đặt hành động, IndexedDB cũng hỗ trợ các giao dịch và rất phù hợp với lưu trữ một lượng lớn dữ liệu có cấu trúc.

Mỗi cơ sở dữ liệu IndexedDB là duy nhất cho một nguồn gốc (thường là miền trang web hoặc miền con), nghĩa là miền này không thể truy cập hoặc truy cập được theo bất kỳ nguồn gốc nào khác. Giới hạn bộ nhớ dữ liệu của ứng dụng thường lớn, nếu có, nhưng các trình duyệt khác nhau sẽ xử lý các giới hạn và giải phóng dữ liệu theo cách khác. Xem phần Đọc thêm để biết thêm thông tin.

Điều khoản IndexedDB

Cơ sở dữ liệu
Cấp cao nhất của IndexedDB. Lớp này chứa các kho lưu trữ đối tượng, do đó, chứa dữ liệu mà bạn muốn duy trì. Bạn có thể tạo nhiều cơ sở dữ liệu bằng bất cứ tên nào bạn chọn.
Kho lưu trữ đối tượng
Một bộ chứa riêng lẻ để lưu trữ dữ liệu, tương tự như các bảng trong cơ sở dữ liệu quan hệ. Thông thường, có một kho lưu trữ đối tượng cho mỗi loại (không phải dữ liệu JavaScript loại) dữ liệu nào bạn đang lưu trữ. Không giống như trong các bảng cơ sở dữ liệu, dữ liệu JavaScript các loại dữ liệu trong một cửa hàng không cần phải nhất quán. Ví dụ: nếu một ứng dụng có một kho lưu trữ đối tượng people chứa thông tin về 3 người, đó là thuộc tính độ tuổi của mọi người có thể là 53, 'twenty-five'unknown.
Chỉ mục
Một loại kho lưu trữ đối tượng để sắp xếp dữ liệu trong một kho lưu trữ đối tượng khác (được gọi là kho đối tượng tham chiếu) theo một thuộc tính riêng lẻ của dữ liệu. Chỉ mục được sử dụng để truy xuất các bản ghi trong kho đối tượng của thuộc tính này. Ví dụ: nếu bạn đang ở lưu trữ người khác, bạn có thể muốn tìm nạp họ sau theo tên, tuổi hoặc con vật yêu thích của bạn.
Hoạt động
Tương tác với cơ sở dữ liệu.
Giao dịch
Một trình bao bọc xung quanh một thao tác hoặc nhóm thao tác để đảm bảo cơ sở dữ liệu tính toàn vẹn. Nếu một trong số các hành động trong giao dịch không thành công, thì không có hành động nào và cơ sở dữ liệu trở về trạng thái trước khi giao dịch đầu. Tất cả thao tác đọc hoặc ghi trong IndexedDB phải là một phần của giao dịch. Việc này cho phép các thao tác đọc-sửa đổi-ghi nguyên tử mà không có nguy cơ xảy ra xung đột với các luồng khác hoạt động cùng lúc trên cơ sở dữ liệu.
Con trỏ
Cơ chế lặp lại qua nhiều bản ghi trong một cơ sở dữ liệu.

Cách kiểm tra khả năng hỗ trợ IndexedDB

IndexedDB hầu như được hỗ trợ toàn cầu. Tuy nhiên, nếu đang làm việc với các trình duyệt cũ hơn thì bạn cũng không nên phát hiện tính năng. Cách dễ nhất là kiểm tra window đối tượng:

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

Cách mở cơ sở dữ liệu

Nhờ IndexedDB, bạn có thể tạo nhiều cơ sở dữ liệu với bất kỳ tên nào mà mình chọn. Nếu cơ sở dữ liệu không tồn tại khi bạn cố mở nó, được tạo tự động. Để mở cơ sở dữ liệu, hãy dùng phương thức openDB() trong thư viện 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();

Phương thức này trả về một dữ liệu xác thực (pointer) sẽ phân giải thành một đối tượng cơ sở dữ liệu. Khi sử dụng Phương thức openDB(), cung cấp tên, số phiên bản và đối tượng sự kiện để đặt cơ sở dữ liệu.

Dưới đây là ví dụ về phương thức openDB() trong ngữ cảnh:

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

Đặt yêu cầu kiểm tra khả năng hỗ trợ IndexedDB ở đầu hàm ẩn danh. Chiến dịch này thoát khỏi hàm này nếu trình duyệt không hỗ trợ IndexedDB. Nếu hàm có thể Tiếp tục, phương thức này sẽ gọi phương thức openDB() để mở cơ sở dữ liệu có tên 'test-db1'. Trong ví dụ này, chúng tôi đã loại bỏ đối tượng sự kiện không bắt buộc để giữ lại mọi thứ đơn giản, nhưng bạn cần chỉ định mã này để thực hiện bất kỳ công việc có ý nghĩa nào với IndexedDB.

Cách làm việc với kho lưu trữ đối tượng

Cơ sở dữ liệu IndexedDB chứa một hoặc nhiều cửa hàng đối tượng, trong đó mỗi kho lưu trữ đối tượng có một và một cột khác cho dữ liệu được liên kết với khoá đó.

Tạo kho lưu trữ đối tượng

Cơ sở dữ liệu IndexedDB có cấu trúc tốt nên có một kho lưu trữ đối tượng cho mỗi loại dữ liệu cần được duy trì. Ví dụ: một trang web duy trì người dùng các hồ sơ và ghi chú có thể có kho đối tượng people chứa person và một kho lưu trữ đối tượng notes chứa các đối tượng note.

Để đảm bảo tính toàn vẹn của cơ sở dữ liệu, bạn chỉ có thể tạo hoặc xoá kho lưu trữ đối tượng trong đối tượng sự kiện trong lệnh gọi openDB(). Đối tượng sự kiện hiển thị một upgrade() phương thức cho phép bạn tạo kho lưu trữ đối tượng. Gọi createObjectStore() phương thức bên trong phương thức upgrade() để tạo kho đối tượng:

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

Phương thức này lấy tên của kho đối tượng và một cấu hình không bắt buộc cho phép bạn xác định các thuộc tính khác nhau cho kho đối tượng.

Sau đây là ví dụ về cách sử dụng 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();

Trong ví dụ này, một đối tượng sự kiện được truyền đến phương thức openDB() để tạo kho lưu trữ đối tượng và như trước đây, công việc tạo kho lưu trữ đối tượng đã hoàn tất trong phương thức upgrade() của đối tượng sự kiện. Tuy nhiên, vì trình duyệt gửi mã Nếu bạn cố gắng tạo một cửa hàng đối tượng đã tồn tại, bạn nên gói phương thức createObjectStore() trong câu lệnh if để kiểm tra liệu kho đối tượng có tồn tại hay không. Trong khối if, hãy gọi createObjectStore() để tạo kho đối tượng có tên 'firstOS'.

Cách xác định khoá chính

Khi xác định kho lưu trữ đối tượng, bạn có thể xác định cách dữ liệu được xác định duy nhất trong bằng cách sử dụng khoá chính. Bạn có thể xác định khoá chính bằng cách xác định đường dẫn khoá hoặc bằng cách sử dụng một trình tạo khoá.

Đường dẫn khoá là một thuộc tính luôn tồn tại và chứa một giá trị duy nhất. Cho Ví dụ: trong trường hợp kho đối tượng people, bạn có thể chọn email làm đường dẫn chính:

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

Ví dụ này sẽ tạo một kho lưu trữ đối tượng có tên là 'people' và gán email làm khoá chính trong tuỳ chọn keyPath.

Bạn cũng có thể sử dụng một trình tạo khoá như autoIncrement. Công cụ tạo khoá sẽ tạo một giá trị duy nhất cho mỗi đối tượng được thêm vào kho đối tượng. Theo mặc định, nếu bạn không chỉ định một khoá, IndexedDB sẽ tạo một khoá và lưu trữ khoá đó riêng biệt khỏi dữ liệu.

Ví dụ sau đây sẽ tạo một kho đối tượng có tên là 'notes' và thiết lập giá trị khoá chính được gán tự động dưới dạng số tăng tự động:

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

Ví dụ sau tương tự như ví dụ trước, nhưng lần này giá trị tự động tăng được chỉ định rõ ràng cho thuộc tính có tên '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();

Việc chọn phương thức sử dụng để xác định khoá sẽ tuỳ thuộc vào dữ liệu của bạn. Nếu Dữ liệu của bạn có một thuộc tính luôn là duy nhất, nên bạn có thể đặt thuộc tính đó làm keyPath để thực thi tính duy nhất này. Nếu không, hãy sử dụng giá trị tự động tăng.

Đoạn mã sau đây tạo ra ba kho lưu trữ đối tượng thể hiện nhiều cách xác định khoá chính trong kho lưu trữ đối tượng:

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

Cách xác định chỉ mục

Chỉ mục là một loại kho lưu trữ đối tượng dùng để truy xuất dữ liệu từ tham chiếu kho đối tượng của một thuộc tính cụ thể. Chỉ mục nằm bên trong đối tượng tham chiếu lưu trữ và chứa cùng dữ liệu, nhưng sử dụng thuộc tính được chỉ định làm thuộc tính thay vì khoá chính của kho tham chiếu. Chỉ mục phải được lập khi bạn tạo kho lưu trữ đối tượng và có thể dùng để xác định một quy tắc ràng buộc duy nhất trên dữ liệu của bạn.

Để tạo chỉ mục, hãy gọi createIndex() trên thực thể kho đối tượng:

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

Phương thức này tạo và trả về một đối tượng chỉ mục. Phương thức createIndex() trên thực thể của kho đối tượng lấy tên của chỉ mục mới làm chỉ mục đầu tiên và đối số thứ hai đề cập đến thuộc tính trên dữ liệu mà bạn muốn chỉ mục. Đối số cuối cùng cho phép bạn xác định hai tuỳ chọn xác định cách chỉ mục hoạt động: uniquemultiEntry. Nếu bạn đặt unique thành true, chỉ mục không cho phép các giá trị trùng lặp cho một khóa. Tiếp theo, multiEntry xác định cách hoạt động của createIndex() khi thuộc tính được lập chỉ mục là một mảng. Nếu thuộc tính này được đặt thành true, createIndex() sẽ thêm một mục trong chỉ mục cho mỗi mảng . Nếu không, hàm này sẽ thêm một mục nhập chứa mảng.

Ví dụ:

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

Trong ví dụ này, kho lưu trữ đối tượng 'people''notes' có chỉ mục. Người nhận tạo chỉ mục, trước tiên hãy chỉ định kết quả của createObjectStore() (một đối tượng store) thành một biến để bạn có thể gọi createIndex() trên biến đó.

Cách làm việc với dữ liệu

Phần này mô tả cách tạo, đọc, cập nhật và xoá dữ liệu. Các tất cả các thao tác đều không đồng bộ, sử dụng các lời hứa mà API IndexedDB sử dụng yêu cầu. Điều này giúp đơn giản hoá API. Thay vì theo dõi các sự kiện được kích hoạt bởi yêu cầu này, bạn có thể gọi .then() trên đối tượng cơ sở dữ liệu được trả về từ Phương thức openDB() để bắt đầu các tương tác với cơ sở dữ liệu hoặc await sáng tạo.

Tất cả các thao tác dữ liệu trong IndexedDB đều được thực hiện bên trong giao dịch. Một phép toán có dạng như sau:

  1. Nhận đối tượng cơ sở dữ liệu.
  2. Mở giao dịch trên cơ sở dữ liệu.
  3. Mở kho đối tượng khi giao dịch.
  4. Thực hiện thao tác trên kho lưu trữ đối tượng.

Giao dịch có thể được coi là một trình bao bọc an toàn xung quanh một hoạt động hoặc nhóm hoạt động. Nếu một trong các hành động trong giao dịch không thành công, tất cả được khôi phục. Giao dịch cụ thể cho một hoặc nhiều cửa hàng đối tượng, mà bạn xác định thời điểm mở giao dịch. Có thể ở chế độ chỉ đọc hoặc đọc và viết. Điều này cho biết liệu các thao tác trong giao dịch có đọc hoặc thay đổi cơ sở dữ liệu.

Tạo dữ liệu

Để tạo dữ liệu, hãy gọi add() trên thực thể cơ sở dữ liệu và truyền dữ liệu bạn muốn thêm vào. add() đối số đầu tiên của phương thức là kho đối tượng mà bạn muốn thêm dữ liệu vào và đối số thứ hai là một đối tượng chứa các trường và dữ liệu liên quan mà bạn muốn để thêm. Dưới đây là ví dụ đơn giản nhất, trong đó một hàng dữ liệu được thêm vào:

import {openDB} from 'idb';

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

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

addItemToStore();

Mỗi lệnh gọi add() xảy ra trong một giao dịch, vì vậy, ngay cả khi lời hứa đã được thực hiện thành công thì không nhất thiết có nghĩa là thao tác đó có hiệu quả. Để đảm bảo thao tác thêm đã được thực hiện, bạn cần kiểm tra xem toàn bộ giao dịch đã hoàn tất bằng phương thức transaction.done(). Đây là một lời hứa sẽ tự thực hiện khi giao dịch tự hoàn tất và từ chối nếu lỗi giao dịch. Bạn phải thực hiện kiểm tra này đối với tất cả thao tác "viết" hoạt động, bởi vì đó là cách duy nhất để biết những thay đổi đối với cơ sở dữ liệu thực sự đã xảy ra.

Đoạn mã sau đây cho thấy việc sử dụng phương thức add() trong một giao dịch:

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

Khi mở cơ sở dữ liệu (và tạo kho lưu trữ đối tượng nếu cần), bạn sẽ cần để mở một giao dịch bằng cách gọi phương thức transaction() trên giao dịch đó. Phương thức này lấy một đối số cho cửa hàng bạn muốn giao dịch, cũng như chế độ. Trong trường hợp này, chúng ta muốn ghi nhận thông tin cho cửa hàng, vì vậy, ví dụ này chỉ định 'readwrite'.

Bước tiếp theo là bắt đầu thêm các mặt hàng vào cửa hàng như một phần của giao dịch. Trong ví dụ trước, chúng ta đang xử lý 3 toán tử trên 'foods' mỗi kho hàng trả lại một lời hứa:

  1. Đang thêm một bản ghi cho một chiếc bánh sandwich ngon.
  2. Đang thêm bản ghi cho một số quả trứng.
  3. Tín hiệu cho biết giao dịch đã hoàn tất (tx.done).

Bởi vì tất cả những hành động này đều dựa trên hứa hẹn, chúng ta cần phải đợi tất cả để hoàn thành. Chuyển những lời hứa này đến Promise.all là một cách hiệu quả để thực hiện việc này. Promise.all chấp nhận một mảng hứa hẹn và kết thúc khi mọi hứa hẹn đã được giải quyết xong.

Đối với 2 bản ghi đang được thêm, giao diện store của thực thể giao dịch gọi add() rồi truyền dữ liệu đến đó. Bạn có thể await cuộc gọi Promise.all để nó kết thúc khi giao dịch hoàn tất.

Đọc dữ liệu

Để đọc dữ liệu, hãy gọi get() trên thực thể cơ sở dữ liệu mà bạn truy xuất bằng cách sử dụng phương thức openDB(). get() lấy tên cửa hàng và giá trị khoá chính của đối tượng mà bạn muốn truy xuất. Dưới đây là ví dụ cơ bản:

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

Tương tự như với add(), phương thức get() trả về một lời hứa (hứa hẹn) để bạn có thể await nếu phương thức này mà bạn muốn, hoặc sử dụng lệnh gọi lại .then() của lời hứa.

Ví dụ sau đây sử dụng phương thức get() trên cơ sở dữ liệu 'test-db4' Lưu trữ đối tượng 'foods' để lấy một hàng theo khoá chính '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();

Việc truy xuất một hàng từ cơ sở dữ liệu khá đơn giản: mở cơ sở dữ liệu và chỉ định kho lưu trữ đối tượng và giá trị khoá chính của hàng mà bạn muốn lấy dữ liệu từ đó. Vì phương thức get() trả về một hứa hẹn, nên bạn có thể await nó.

Cập nhật dữ liệu

Để cập nhật dữ liệu, hãy gọi put() trên kho đối tượng. Phương thức put() tương tự như phương thức add() và cũng có thể được dùng thay cho add() để tạo dữ liệu. Sau đây là một ví dụ cơ bản sử dụng put() để cập nhật một hàng trong kho đối tượng theo giá trị khoá chính:

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

Giống như các phương thức khác, phương thức này trả về một hàm hứa hẹn. Bạn cũng có thể sử dụng put() làm một phần của giao dịch. Sau đây là một ví dụ về cách sử dụng cửa hàng 'foods' trước đó cập nhật giá của bánh sandwich và trứng:

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

Cách các mục được cập nhật tuỳ thuộc vào cách bạn đặt khoá. Nếu bạn đặt keyPath, mỗi hàng trong kho đối tượng được liên kết với một khoá cùng dòng. Cụm từ trước ví dụ: cập nhật các hàng dựa trên khoá này và khi bạn cập nhật các hàng trong khoá này trong trường hợp cụ thể, bạn cần chỉ định khoá đó để cập nhật mục thích hợp trong kho đối tượng. Bạn cũng có thể tạo khoá ngoại tuyến bằng cách đặt một autoIncrement làm khoá chính.

Xoá dữ liệu

Để xoá dữ liệu, hãy gọi delete() trên kho đối tượng:

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

Giống như add()put(), bạn có thể sử dụng dữ liệu này như một phần của giao dịch:

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

Cấu trúc của hoạt động tương tác với cơ sở dữ liệu giống như cấu trúc của tương tác khác các toán tử. Hãy nhớ kiểm tra để đảm bảo rằng toàn bộ giao dịch đã hoàn tất trước bao gồm cả phương thức tx.done trong mảng mà bạn truyền đến Promise.all.

Tải tất cả dữ liệu

Từ trước đến nay, bạn chỉ truy xuất từng đối tượng từ cửa hàng. Bạn cũng có thể truy xuất tất cả dữ liệu hoặc một tập hợp con từ kho lưu trữ đối tượng hoặc chỉ mục bằng cách sử dụng phương thức getAll() hoặc con trỏ.

Phương thức getAll()

Cách đơn giản nhất để truy xuất tất cả dữ liệu của kho đối tượng là gọi getAll() trên kho lưu trữ hoặc chỉ mục đối tượng, như sau:

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

Phương thức này trả về tất cả đối tượng trong kho lưu trữ đối tượng mà không có quy tắc ràng buộc bất kỳ điều gì. Đây là cách trực tiếp nhất để nhận tất cả giá trị từ kho lưu trữ đối tượng, nhưng cũng ít linh hoạt nhất.

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

Ví dụ này gọi getAll() trên kho đối tượng 'foods'. Thao tác này sẽ trả về tất cả các đối tượng từ 'foods', được sắp xếp theo khoá chính.

Cách sử dụng con trỏ

Con trỏ là một cách linh hoạt hơn để truy xuất nhiều đối tượng. Một con trỏ chọn từng đối tượng trong kho lưu trữ đối tượng hoặc lập chỉ mục từng đối tượng, cho phép bạn làm một việc gì đó với dữ liệu khi được chọn. Con trỏ, giống như các thao tác khác với cơ sở dữ liệu, hoạt động trong giao dịch.

Để tạo con trỏ, hãy gọi openCursor() trên kho đối tượng như một phần của giao dịch. Sử dụng cửa hàng 'foods' từ các ví dụ trước, đây là cách di chuyển con trỏ qua tất cả các hàng dữ liệu trong kho lưu trữ đối tượng:

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

Giao dịch trong trường hợp này sẽ được mở ở chế độ 'readonly' và Phương thức openCursor được gọi. Trong vòng lặp while tiếp theo, hàng ở vị trí hiện tại của con trỏ có thể đọc các thuộc tính keyvalue, và bạn có thể hoạt động dựa trên các giá trị đó theo bất cứ cách nào phù hợp nhất với . Khi đã sẵn sàng, bạn có thể gọi continue() của đối tượng cursor để chuyển đến hàng tiếp theo và vòng lặp while kết thúc khi con trỏ đến cuối tập dữ liệu.

Sử dụng con trỏ có dải ô và chỉ mục

Chỉ mục cho phép bạn tìm nạp dữ liệu trong kho lưu trữ đối tượng bằng một thuộc tính khác với khoá chính. Bạn có thể tạo chỉ mục trên bất kỳ thuộc tính nào, chỉ mục này trở thành keyPath cho chỉ mục, chỉ định một dải ô trên thuộc tính đó và nhận dữ liệu trong thuộc tính dải ô bằng getAll() hoặc con trỏ.

Xác định dải ô bằng cách sử dụng đối tượng IDBKeyRange. và bất kỳ điều kiện nào sau đây phương thức:

Phương thức upperBound()lowerBound() chỉ định giới hạn trên và giới hạn dưới của dải ô.

IDBKeyRange.lowerBound(indexKey);

hoặc:

IDBKeyRange.upperBound(indexKey);

Mỗi kết quả sẽ nhận một đối số: giá trị keyPath của chỉ mục cho mục bạn muốn để chỉ định làm giới hạn trên hoặc giới hạn dưới.

Phương thức bound() chỉ định cả giới hạn trên và giới hạn dưới:

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);

Phạm vi của các hàm này được bao gồm theo mặc định, tức là phạm vi này bao gồm dữ liệu được chỉ định làm giới hạn của dải ô. Để loại bỏ các giá trị đó, chỉ định dải ô là loại trừ bằng cách truyền true làm đối số thứ hai cho lowerBound() hoặc upperBound() hay dưới dạng đối số thứ ba và thứ tư của bound() cho giới hạn dưới và giới hạn trên tương ứng.

Ví dụ tiếp theo sử dụng một chỉ mục trên thuộc tính 'price' trong đối tượng 'foods' của bạn. Cửa hàng hiện cũng có một biểu mẫu đính kèm với hai thông tin đầu vào cho giới hạn trên và dưới của dải ô. Sử dụng mã sau để tìm thực phẩm bằng giữa các giới hạn đó:

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

Trước tiên, mã ví dụ lấy giá trị cho các giới hạn rồi kiểm tra xem các giới hạn đó có đã tồn tại. Khối mã tiếp theo sẽ quyết định phương thức nào sẽ được sử dụng để giới hạn phạm vi dựa trên các giá trị. Trong hoạt động tương tác với cơ sở dữ liệu, hãy mở kho lưu trữ đối tượng trên giao dịch như bình thường, sau đó mở chỉ mục 'price' trên kho đối tượng. Chiến lược phát hành đĩa đơn Chỉ mục 'price' cho phép bạn tìm kiếm các mặt hàng theo giá.

Sau đó, mã này sẽ mở một con trỏ trên chỉ mục và chuyển vào dải ô. Con trỏ sẽ trả về một lời hứa (promise) đại diện cho đối tượng đầu tiên trong dải ô hoặc undefined nếu không có dữ liệu trong phạm vi. Phương thức cursor.continue() trả về một con trỏ biểu thị đối tượng tiếp theo và tiếp tục xuyên suốt vòng lặp cho đến khi bạn đến cuối phạm vi.

Tạo phiên bản cơ sở dữ liệu

Khi gọi phương thức openDB(), bạn có thể chỉ định số phiên bản cơ sở dữ liệu trong tham số thứ hai. Trong tất cả các ví dụ trong hướng dẫn này, phiên bản đã được được đặt thành 1, nhưng bạn có thể nâng cấp cơ sở dữ liệu lên phiên bản mới nếu cần sửa đổi nó theo một cách nào đó. Nếu phiên bản được chỉ định lớn hơn phiên bản của cơ sở dữ liệu hiện có, lệnh gọi lại upgrade trong đối tượng sự kiện sẽ thực thi, cho phép bạn thêm các kho đối tượng và chỉ mục mới vào cơ sở dữ liệu.

Đối tượng db trong lệnh gọi lại upgrade có một thuộc tính oldVersion đặc biệt, Mã này cho biết số phiên bản của cơ sở dữ liệu mà trình duyệt có quyền truy cập. Bạn có thể chuyển số phiên bản này vào câu lệnh switch để thực thi các khối mã bên trong lệnh gọi lại upgrade dựa trên phiên bản cơ sở dữ liệu hiện có số. Ví dụ:

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

Ví dụ này thiết lập phiên bản mới nhất của cơ sở dữ liệu thành 2. Trường hợp mã này thực thi lần đầu tiên, cơ sở dữ liệu vẫn chưa tồn tại trong trình duyệt, vì vậy oldVersion0 và câu lệnh switch bắt đầu tại case 0. Trong ví dụ này, thêm kho lưu trữ đối tượng 'store' vào cơ sở dữ liệu.

Điểm chính: Trong các câu lệnh switch, thường có một break sau mỗi case nhưng cố ý không sử dụng khối này ở đây. Bằng cách này, nếu cơ sở dữ liệu phải có một vài phiên bản hoặc nếu không tồn tại, mã sẽ tiếp tục qua các khối case còn lại cho đến khi thông tin được cập nhật. Vì vậy, trong ví dụ, trình duyệt sẽ tiếp tục thực thi thông qua case 1, tạo một chỉ mục name trên Kho lưu trữ đối tượng của store.

Để tạo chỉ mục 'description' trên kho lưu trữ đối tượng 'store', hãy cập nhật số phiên bản và thêm một khối case mới như sau:

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

Nếu cơ sở dữ liệu mà bạn đã tạo trong ví dụ trước vẫn tồn tại trong trình duyệt, khi lệnh này thực thi, oldVersion sẽ là 2. Trình duyệt bỏ qua case 0case 1 rồi thực thi mã trong case 2, việc này sẽ tạo ra một description chỉ mục. Sau đó, trình duyệt có cơ sở dữ liệu ở phiên bản 3 chứa store kho đối tượng có chỉ mục namedescription.

Tài liệu đọc thêm

Các tài nguyên sau cung cấp thêm thông tin và ngữ cảnh cho việc sử dụng IndexedDB.

Tài liệu IndexedDB

Hạn mức bộ nhớ dữ liệu