Hướng dẫn này trình bày những thông tin cơ bản về
IndexedDB API.
Chúng tôi đang sử dụng thư viện IndexedDB Promised của Jake Archibald. Thư viện này rất giống với API IndexedDB, nhưng sử dụng lời hứa mà 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 vẫn duy trì cấu trúc của API.
IndexedDB là gì?
IndexedDB là một hệ thống lưu trữ NoSQL quy mô lớn, cho phép lưu trữ hầu hết mọi thứ trong trình duyệt của người dùng. Ngoài các thao tác tìm kiếm, lấy và đặt thông thường, IndexedDB cũng hỗ trợ các giao dịch và rất phù hợp để lưu trữ 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 hoặc miền con của trang web), nghĩa là cơ sở dữ liệu này không thể truy cập hoặc được truy cập bởi bất kỳ nguồn gốc nào khác. Giới hạn lưu trữ dữ liệu của API này thường lớn (nếu có), nhưng mỗi trình duyệt lại xử lý giới hạn và việc loại bỏ dữ liệu theo cách khác nhau. Hãy xem phần Tài liệu đọc thêm để biết thêm thông tin.
Thuật ngữ IndexedDB
- Cơ sở dữ liệu
- Cấp cao nhất của IndexedDB. Cơ sở dữ liệu này chứa các kho lưu trữ đối tượng, trong đó có 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 với bất kỳ 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 kiểu dữ liệu JavaScript) dữ liệu mà bạn đang lưu trữ. Không giống như trong bảng cơ sở dữ liệu, các kiểu dữ liệu JavaScript của dữ liệu trong kho lưu trữ không cần phải nhất quán. Ví dụ: nếu một ứng dụng có kho lưu trữ đối tượng
peoplechứa thông tin về 3 người, thì thuộc tính tuổi của những người đó có thể là53,'twenty-five'và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 (gọi là kho lưu trữ đối tượng tham chiếu) theo một thuộc tính riêng của dữ liệu. Chỉ mục được dùng để truy xuất các bản ghi trong kho lưu trữ đối tượng theo thuộc tính này. Ví dụ: nếu đang lưu trữ thông tin về người, bạn có thể muốn tìm nạp thông tin đó sau theo tên, tuổi hoặc loài động vật yêu thích của họ.
- Hoạt động
- Một lượt 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 tính toàn vẹn của cơ sở dữ liệu. Nếu một trong các hành động trong giao dịch không thành công, thì không có hành động nào được áp dụng và cơ sở dữ liệu sẽ quay về trạng thái trước khi giao dịch bắt đầ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. Điều này cho phép các thao tác đọc-sửa đổi-ghi nguyên tử mà không có nguy cơ xung đột với các luồng khác hoạt động trên cơ sở dữ liệu cùng một lúc.
- Cursor
- Một cơ chế để lặp lại nhiều bản ghi trong cơ sở dữ liệu.
Cách kiểm tra khả năng hỗ trợ IndexedDB
IndexedDB gần như được hỗ trợ trên toàn cầu.
Tuy nhiên, nếu bạn đang làm việc với các trình duyệt cũ, thì bạn nên
phát hiện tính năng hỗ trợ phòng trường hợp cần thiết. Cách dễ nhất là kiểm tra đối tượng 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();
Cách mở cơ sở dữ liệu
Với IndexedDB, bạn có thể tạo nhiều cơ sở dữ liệu với bất kỳ tên nào bạn chọn. Nếu cơ sở dữ liệu không tồn tại khi bạn cố gắng mở, thì cơ sở dữ liệu đó sẽ tự động được tạo.
Để mở cơ sở dữ liệu, hãy sử dụng phương thức openDB() từ 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 lời hứa phân giải thành đối tượng cơ sở dữ liệu. Khi sử dụng phương thức openDB(), hãy cung cấp tên, số phiên bản và đối tượng sự kiện để thiết lập 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 quy trình kiểm tra khả năng hỗ trợ IndexedDB ở đầu hàm ẩn danh. Thao tác này sẽ thoát khỏi hàm nếu trình duyệt không hỗ trợ IndexedDB. Nếu hàm có thể tiếp tục, hàm sẽ gọi phương thức openDB() để mở cơ sở dữ liệu có tên là 'test-db1'.
Trong ví dụ này, đối tượng sự kiện không bắt buộc đã bị bỏ qua để giữ cho mọi thứ đơn giản, nhưng bạn cần chỉ định đối tượng 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 kho lưu trữ đối tượng, mỗi kho lưu trữ có một cột cho khoá và một cột khác cho dữ liệu liên kết với khoá đó.
Tạo kho đối tượng
Một cơ sở dữ liệu IndexedDB có cấu trúc tốt phải 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 lưu giữ hồ sơ người dùng và ghi chú có thể có kho lưu trữ đối tượng people chứa các đối tượng person và 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 phương thức upgrade() cho phép bạn tạo kho lưu trữ đối tượng. Gọi phương thức createObjectStore() bên trong phương thức upgrade() để tạo kho lưu trữ đố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 lưu trữ đối tượng và một đối tượng cấu hình không bắt buộc cho phép bạn xác định nhiều thuộc tính cho kho lưu trữ đố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, công việc tạo kho lưu trữ đối tượng được thực hiện trong phương thức upgrade() của đối tượng sự kiện. Tuy nhiên, vì trình duyệt sẽ gửi lỗi nếu bạn cố gắng tạo một kho lưu trữ đối tượng đã tồn tại, nên bạn nên gói phương thức createObjectStore() trong câu lệnh if để kiểm tra xem kho lưu trữ đối tượng có tồn tại hay không. Bên trong khối if, hãy gọi createObjectStore() để tạo một kho lưu trữ đối tượng có tên là '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 kho lưu trữ bằ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 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. Ví dụ: trong trường hợp kho lưu trữ đối tượng people, bạn có thể chọn địa chỉ email làm đường dẫn khoá:
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 tạo một kho lưu trữ đối tượng có tên là 'people' và chỉ định thuộc tính email làm khoá chính trong tuỳ chọn keyPath.
Bạn cũng có thể sử dụng trình tạo khoá như autoIncrement. Trình tạo khoá tạo một giá trị duy nhất cho mọi đối tượng được thêm vào kho lưu trữ đối tượng. Theo mặc định, nếu bạn không chỉ định khoá, IndexedDB sẽ tạo một khoá và lưu trữ khoá đó tách biệt với dữ liệu.
Ví dụ sau đây tạo một kho lưu trữ đối tượng có tên là 'notes' và đặt khoá chính được chỉ định 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 đây tương tự như ví dụ trước, nhưng lần này giá trị tự động tăng được gán rõ ràng cho một thuộc tính có tên là '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 dùng để xác định khoá phụ 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, thì 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.
Mã sau đây tạo ba kho lưu trữ đối tượng minh hoạ 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ừ kho lưu trữ đối tượng tham chiếu theo một thuộc tính được chỉ định. Chỉ mục nằm trong kho lưu trữ đối tượng tham chiếu và chứa cùng một dữ liệu, nhưng sử dụng thuộc tính được chỉ định làm đường dẫn khoá thay vì khoá chính của kho lưu trữ tham chiếu. Bạn phải tạo chỉ mục khi tạo kho lưu trữ đối tượng và có thể dùng chỉ mục để xác định một quy tắc ràng buộc duy nhất đối với dữ liệu.
Để tạo chỉ mục, hãy gọi phương thức createIndex() trên một thực thể kho lưu trữ đố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 lưu trữ đối tượng lấy tên của chỉ mục mới làm đối số đầu tiên và đối số thứ hai tham chiếu đến thuộc tính trên dữ liệu mà bạn muốn lập 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: unique và multiEntry. Nếu unique được đặt thành true, thì chỉ mục sẽ không cho phép các giá trị trùng lặp cho một khoá. Tiếp theo, multiEntry xác định cách createIndex() hoạt động khi thuộc tính được lập chỉ mục là một mảng. Nếu được đặt thành true, createIndex() sẽ thêm một mục nhập vào chỉ mục cho mỗi phần tử mảng. Nếu không, hàm này sẽ thêm một mục nhập duy nhất 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' và 'notes' có chỉ mục. Để tạo chỉ mục, trước tiên, hãy gán kết quả của createObjectStore() (một đối tượng kho lưu trữ đối tượng) cho một biến để bạn có thể gọi createIndex() trên biến đó.
Cách xử lý 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 thao tác này đều không đồng bộ, sử dụng lời hứa khi 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 do yêu cầu kích hoạt, 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 tương tác với cơ sở dữ liệu hoặc await quá trình tạo cơ sở dữ liệu.
Tất cả thao tác dữ liệu trong IndexedDB đều được thực hiện bên trong một giao dịch. Mỗi toán tử có dạng như sau:
- Lấy đối tượng cơ sở dữ liệu.
- Mở giao dịch trên cơ sở dữ liệu.
- Mở kho lưu trữ đối tượng trong giao dịch.
- Thực hiện thao tác trên kho lưu trữ đối tượng.
Bạn có thể coi giao dịch là một trình bao bọc an toàn cho một thao tác hoặc nhóm thao tác. Nếu một trong các hành động trong giao dịch không thành công, thì tất cả các hành động sẽ được khôi phục. Giao dịch dành riêng cho một hoặc nhiều kho lưu trữ đối tượng mà bạn xác định khi mở giao dịch. Các quyền này có thể là chỉ đọc hoặc đọc và ghi. Điều này cho biết liệu các thao tác bên trong giao dịch có đọc dữ liệu hay thực hiện thay đổi đối với cơ sở dữ liệu hay không.
Tạo dữ liệu
Để tạo dữ liệu, hãy gọi phương thức add() trên phiên bản cơ sở dữ liệu và truyền dữ liệu bạn muốn thêm. Đối số đầu tiên của phương thức add() là kho đối tượng mà bạn muốn thêm dữ liệu vào, còn đố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() diễn ra trong một giao dịch, vì vậy, ngay cả khi lời hứa được giải quyết thành công, thì điều đó không nhất thiết có nghĩa là thao tác đã hoạt động. Để đả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 hay chưa bằng phương thức transaction.done(). Đây là một lời hứa sẽ giải quyết khi giao dịch tự hoàn tất và từ chối nếu giao dịch gặp lỗi. Bạn phải thực hiện quy trình kiểm tra này cho tất cả các thao tác "ghi", vì đây là cách duy nhất để biết các thay đổi đối với cơ sở dữ liệu đã thực sự diễn ra.
Đoạn mã sau đây cho thấy cách sử dụng phương thức add() bên 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();
Sau khi mở cơ sở dữ liệu (và tạo kho lưu trữ đối tượng nếu cần), bạn cần mở một giao dịch bằng cách gọi phương thức transaction() trên đó. Phương thức này
lấy một đối số cho cửa hàng mà bạn muốn giao dịch, cũng như chế độ.
Trong trường hợp này, chúng ta muốn ghi vào kho lưu trữ, 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 trong giao dịch.
Trong ví dụ trước, chúng ta đang xử lý ba thao tác trên kho lưu trữ 'foods', mỗi thao tác trả về một lời hứa:
- Thêm bản ghi cho một chiếc bánh sandwich ngon.
- Đang thêm bản ghi cho một số quả trứng.
- Báo hiệu rằng giao dịch đã hoàn tất (
tx.done).
Vì tất cả các hành động này đều dựa trên lời hứa, nên chúng ta cần đợi tất cả các hành động này hoàn tất. Việc truyền các lời hứa này đến Promise.all là một cách hay và tiện lợi để thực hiện việc này. Promise.all chấp nhận một mảng các lời hứa và kết thúc khi tất cả các lời hứa được truyền đến nó đã được giải quyết.
Đối với hai bản ghi đang được thêm, giao diện store của thực thể giao dịch sẽ gọi add() và truyền dữ liệu đến đó. Bạn có thể await lệnh gọi Promise.all để lệnh gọi này kết thúc khi giao dịch hoàn tất.
Đọc dữ liệu
Để đọc dữ liệu, hãy gọi phương thức get() trên thực thể cơ sở dữ liệu mà bạn truy xuất bằng phương thức openDB().
get() lấy tên của kho lưu trữ và giá trị khoá chính của đối tượng mà bạn muốn truy xuất. Sau đây là một 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();
Giống như add(), phương thức get() trả về một lời hứa, vì vậy, bạn có thể await nếu 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 kho lưu trữ đối tượng 'foods' của cơ sở dữ liệu 'test-db4' để lấy một hàng duy nhất 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 cũng như giá trị khoá chính của hàng mà bạn muốn lấy dữ liệu. Vì phương thức get() trả về một lời hứa, nên bạn có thể await phương thức đó.
Cập nhật dữ liệu
Để cập nhật dữ liệu, hãy gọi phương thức put() trên kho lưu trữ đố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. Dưới đây là ví dụ cơ bản về cách sử dụng put() để cập nhật một hàng trong kho lưu trữ đố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 lời hứa. Bạn cũng có thể sử dụng put() trong một giao dịch. Sau đây là ví dụ về cách dùng cửa hàng 'foods' ở trên để cập nhật giá của bánh mì kẹp 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ập nhật các mục phụ thuộc vào cách bạn đặt khoá. Nếu bạn đặt keyPath, thì mỗi hàng trong kho lưu trữ đối tượng sẽ được liên kết với một khoá cùng dòng. Ví dụ trước cập nhật các hàng dựa trên khoá này và khi cập nhật các hàng trong trường hợp này, bạn cần chỉ định khoá đó để cập nhật mục thích hợp trong kho lưu trữ đối tượng. Bạn cũng có thể tạo khoá ngoài dòng bằng cách đặt autoIncrement làm khoá chính.
Xoá dữ liệu
Để xoá dữ liệu, hãy gọi phương thức delete() trên kho lưu trữ đố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() và put(), bạn có thể sử dụng phương thức này trong một 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 cũng giống như các hoạt động khác. Hãy nhớ kiểm tra để đảm bảo toàn bộ giao dịch đã hoàn tất bằng cách đưa phương thức tx.done vào mảng mà bạn truyền đến Promise.all.
Đang lấy tất cả dữ liệu
Cho đến nay, bạn chỉ truy xuất từng đối tượng một từ kho lưu trữ. 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 lưu trữ đối tượng là gọi getAll() trên kho lưu trữ đối tượng hoặc chỉ mục, 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ó bất kỳ quy tắc ràng buộc nào. Đây là cách trực tiếp nhất để lấy tất cả giá trị từ một kho lưu trữ đối tượng, nhưng cũng là cách kém 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 lưu trữ đối tượng 'foods'. Thao tác này sẽ trả về tất 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. Con trỏ chọn từng đối tượng trong kho lưu trữ đối tượng hoặc chỉ mục, cho phép bạn thực hiện thao tác nào đó với dữ liệu khi dữ liệu được chọn. Con trỏ, giống như các thao tác khác trên cơ sở dữ liệu, hoạt động trong các giao dịch.
Để tạo con trỏ, hãy gọi openCursor() trên kho lưu trữ đối tượng trong một giao dịch. Sử dụng kho lưu trữ '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();
Trong trường hợp này, giao dịch được mở ở chế độ 'readonly' và phương thức openCursor của giao dịch được gọi. Trong vòng lặp while tiếp theo, hàng tại vị trí hiện tại của con trỏ có thể đọc được các thuộc tính key và value, đồng thời bạn có thể thao tác trên các giá trị đó theo bất kỳ cách nào phù hợp nhất với ứng dụng của mình. Khi đã sẵn sàng, bạn có thể gọi phương thức continue() của đối tượng cursor để chuyển đến hàng tiếp theo và vòng lặp while sẽ kết thúc khi con trỏ đến cuối tập dữ liệu.
Sử dụng con trỏ với 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 theo một thuộc tính không phải là khoá chính. Bạn có thể tạo chỉ mục trên bất kỳ thuộc tính nào (thuộc tính này sẽ trở thành keyPath cho chỉ mục), chỉ định một dải ô trên thuộc tính đó và lấy dữ liệu trong dải ô bằng cách sử dụng getAll() hoặc con trỏ.
Xác định phạm vi bằng đối tượng IDBKeyRange và bất kỳ phương thức nào sau đây:
upperBound().lowerBound().bound()(bao gồm cả hai).only().includes().
Các phương thức upperBound() và lowerBound() chỉ định giới hạn trên và giới hạn dưới của phạm vi.
IDBKeyRange.lowerBound(indexKey);
hoặc:
IDBKeyRange.upperBound(indexKey);
Mỗi hàm nhận một đối số: giá trị keyPath của chỉ mục cho mục mà 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 cho các hàm này được bao gồm theo mặc định, nghĩa là phạm vi này bao gồm cả dữ liệu được chỉ định làm giới hạn của phạm vi. Để loại bỏ các giá trị đó, hãy chỉ định phạm vi là độc quyền bằng cách truyền true làm đối số thứ hai cho lowerBound() hoặc upperBound(), hoặc làm đối số thứ ba và thứ tư của bound(), tương ứng với giới hạn dưới và giới hạn trên.
Ví dụ tiếp theo sử dụng một chỉ mục trên thuộc tính 'price' trong kho lưu trữ đối tượng 'foods'. Cửa hàng hiện cũng có một biểu mẫu đính kèm với hai giá trị đầu vào cho giới hạn trên và giới hạn dưới của phạm vi. Hãy dùng mã sau để tìm các món ăn có giá trong 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ụ sẽ lấy các giá trị cho giới hạn và kiểm tra xem giới hạn có tồn tại hay không. Khối mã tiếp theo quyết định phương thức nào sẽ được 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 lưu trữ đối tượng. Chỉ mục 'price' cho phép bạn tìm kiếm các mặt hàng theo giá.
Sau đó, mã sẽ mở một con trỏ trên chỉ mục và truyền vào dải ô. Con trỏ
trả về một lời hứa đạ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 dải ô. Phương thức cursor.continue() trả về một con trỏ đại diện cho đối tượng tiếp theo và tiếp tục lặp lại cho đến khi bạn đến cuối dải ô.
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ả ví dụ của hướng dẫn này, phiên bản đã đượ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 theo 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ó, thì 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 kho lưu trữ đố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, 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ể truyề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 số phiên bản cơ sở dữ liệu hiện có. 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 đặt phiên bản mới nhất của cơ sở dữ liệu thành 2. Khi mã này
thực thi lần đầu, cơ sở dữ liệu chưa tồn tại trong trình duyệt, vì vậy, oldVersion
là 0 và câu lệnh switch bắt đầu tại case 0. Trong ví dụ này, thao tác này sẽ thêm một kho lưu trữ đối tượng 'store' vào cơ sở dữ liệu.
Điểm mấu chốt: Trong câu lệnh switch, thường có một break sau mỗi khối case, nhưng chúng tôi cố ý không sử dụng ở đây. Bằng cách này, nếu cơ sở dữ liệu hiện có là phiên bản cũ hoặc không tồn tại, thì mã sẽ tiếp tục chạy qua các khối case còn lại cho đến khi được cập nhật. Vì vậy, trong ví dụ này, trình duyệt tiếp tục thực thi thông qua case 1, tạo chỉ mục name trên kho lưu trữ đối tượng 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 bạn đã tạo trong ví dụ trước vẫn tồn tại trong trình duyệt,
thì khi thực thi, oldVersion sẽ là 2. Trình duyệt bỏ qua case 0 và case 1, đồng thời thực thi mã trong case 2 để tạo chỉ mục description. Sau đó, trình duyệt sẽ có một cơ sở dữ liệu ở phiên bản 3 chứa kho lưu trữ đối tượng store có chỉ mục name và description.
Tài liệu đọc thêm
Các tài nguyên sau đây cung cấp thêm thông tin và bối cảnh để sử dụng IndexedDB.
Tài liệu về IndexedDB
idbKho lưu trữ GitHub- Sử dụng IndexedDB
- Các khái niệm cơ bản về IndexedDB
- Quy cách Indexed Database API 3.0