คู่มือนี้ครอบคลุมข้อมูลเบื้องต้นเกี่ยวกับ IndexedDB API
เราใช้ไลบรารี IndexedDB Promised ของ Jake Archibald ซึ่งคล้ายกับ IndexedDB API มาก แต่ใช้ Promise ซึ่งคุณสามารถ await เพื่อให้ได้ไวยากรณ์ที่กระชับยิ่งขึ้น ซึ่งจะช่วยลดความซับซ้อนของ API ในขณะที่ยังคงโครงสร้างไว้
IndexedDB คืออะไร
IndexedDB เป็นระบบจัดเก็บข้อมูล NoSQL ขนาดใหญ่ที่ช่วยให้จัดเก็บข้อมูลได้เกือบทุกอย่างในเบราว์เซอร์ของผู้ใช้ นอกจากจะรองรับการดำเนินการค้นหา รับ และใส่ตามปกติแล้ว IndexedDB ยังรองรับธุรกรรมและเหมาะสำหรับการจัดเก็บข้อมูลที่มีโครงสร้างจำนวนมากด้วย
ฐานข้อมูล IndexedDB แต่ละฐานข้อมูลจะไม่ซ้ำกันสำหรับต้นทาง (โดยปกติคือโดเมนหรือโดเมนย่อยของเว็บไซต์) ซึ่งหมายความว่าจะไม่สามารถเข้าถึงหรือถูกเข้าถึงโดยต้นทางอื่นๆ ได้ ขีดจำกัดพื้นที่เก็บข้อมูลมักจะมีขนาดใหญ่ (หากมี) แต่เบราว์เซอร์ต่างๆ จะจัดการขีดจำกัดและการลบข้อมูลแตกต่างกัน ดูข้อมูลเพิ่มเติมได้ในส่วนอ่านเพิ่มเติม
ข้อกำหนดของ IndexedDB
- ฐานข้อมูล
- ระดับสูงสุดของ IndexedDB ซึ่งประกอบด้วยที่เก็บออบเจ็กต์ ซึ่งมีข้อมูลที่คุณต้องการเก็บไว้ คุณสามารถสร้างฐานข้อมูลได้หลายรายการ โดยใช้ชื่อใดก็ได้ตามต้องการ
- Object Store
- ที่เก็บข้อมูลแต่ละรายการเพื่อจัดเก็บข้อมูล คล้ายกับตารางในฐานข้อมูลเชิงสัมพันธ์
โดยปกติแล้วจะมีที่เก็บออบเจ็กต์ 1 แห่งสำหรับข้อมูลแต่ละประเภท (ไม่ใช่ประเภทข้อมูล JavaScript) ที่คุณจัดเก็บ ประเภทข้อมูล JavaScript ของข้อมูลใน Store ไม่จำเป็นต้องสอดคล้องกัน ซึ่งต่างจากในตารางฐานข้อมูล ตัวอย่างเช่น หากแอปมีที่เก็บออบเจ็กต์
peopleซึ่งมีข้อมูลเกี่ยวกับบุคคล 3 คน พร็อพเพอร์ตี้อายุของบุคคลเหล่านั้นอาจเป็น53,'twenty-five'และunknown - Index
- ที่เก็บข้อมูลออบเจ็กต์ประเภทหนึ่งสำหรับจัดระเบียบข้อมูลในที่เก็บข้อมูลออบเจ็กต์อื่น (เรียกว่าที่เก็บข้อมูลออบเจ็กต์อ้างอิง) ตามพร็อพเพอร์ตี้แต่ละรายการของข้อมูล ดัชนีนี้ใช้เพื่อดึงข้อมูลระเบียนในที่เก็บออบเจ็กต์ตามพร็อพเพอร์ตี้นี้ ตัวอย่างเช่น หากคุณจัดเก็บข้อมูลบุคคล คุณอาจต้องการดึงข้อมูลบุคคลเหล่านั้นในภายหลังตามชื่อ อายุ หรือสัตว์ที่ชอบ
- การดำเนินการ
- การโต้ตอบกับฐานข้อมูล
- ธุรกรรม
- Wrapper ที่ครอบคลุมการดำเนินการหรือกลุ่มการดำเนินการที่ช่วยให้มั่นใจถึงความสมบูรณ์ของฐานข้อมูล หากการดำเนินการอย่างใดอย่างหนึ่งในธุรกรรมล้มเหลว ระบบจะไม่ใช้การดำเนินการใดๆ เลย และฐานข้อมูลจะกลับสู่สถานะเดิมก่อนที่ธุรกรรมจะเริ่มต้น การดำเนินการอ่านหรือเขียนทั้งหมดใน IndexedDB ต้องเป็นส่วนหนึ่งของธุรกรรม ซึ่งช่วยให้ดำเนินการอ่าน-แก้ไข-เขียนแบบอะตอมได้โดยไม่มีความเสี่ยงที่จะขัดแย้งกับเทรดอื่นๆ ที่ทำงานในฐานข้อมูลพร้อมกัน
- เคอร์เซอร์
- กลไกในการทำซ้ำระเบียนหลายรายการในฐานข้อมูล
วิธีตรวจสอบการรองรับ 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 ช่วยให้คุณสร้างฐานข้อมูลหลายรายการโดยใช้ชื่อใดก็ได้ตามต้องการ หากไม่มีฐานข้อมูลเมื่อคุณพยายามเปิด ระบบจะสร้างฐานข้อมูลโดยอัตโนมัติ
หากต้องการเปิดฐานข้อมูล ให้ใช้เมธอด openDB() จากไลบรารี 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();
เมธอดนี้จะแสดงผล 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 มีที่เก็บออบเจ็กต์อย่างน้อย 1 รายการ ซึ่งแต่ละรายการมีคอลัมน์สำหรับคีย์ และอีกคอลัมน์หนึ่งสำหรับข้อมูลที่เชื่อมโยงกับคีย์นั้น
สร้างที่เก็บออบเจ็กต์
ฐานข้อมูล IndexedDB ที่มีโครงสร้างที่ดีควรมีที่เก็บออบเจ็กต์ 1 รายการสำหรับข้อมูลแต่ละประเภทที่ต้องเก็บไว้ ตัวอย่างเช่น เว็บไซต์ที่เก็บโปรไฟล์ผู้ใช้และโน้ตไว้อาจมีที่เก็บออบเจ็กต์ people ซึ่งมีออบเจ็กต์ person และที่เก็บออบเจ็กต์ notes ซึ่งมีออบเจ็กต์ note
เพื่อให้มั่นใจถึงความสมบูรณ์ของฐานข้อมูล คุณจะสร้างหรือนำที่เก็บออบเจ็กต์ในออบเจ็กต์เหตุการณ์ออกได้ในการเรียก openDB() เท่านั้น ออบเจ็กต์เหตุการณ์จะแสดงเมธอด upgrade() ที่ช่วยให้คุณสร้างที่เก็บออบเจ็กต์ได้ เรียกใช้เมธอด createObjectStore() ภายในเมธอด upgrade() เพื่อสร้างที่เก็บออบเจ็กต์
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'
วิธีกำหนดคีย์หลัก
เมื่อกำหนด Object Store คุณสามารถกำหนดวิธีระบุข้อมูลที่ไม่ซ้ำกันใน Store โดยใช้คีย์หลัก คุณสามารถกำหนดคีย์หลักได้โดยการกำหนดเส้นทางคีย์หรือใช้เครื่องมือสร้างคีย์
เส้นทางคีย์คือพร็อพเพอร์ตี้ที่มีอยู่เสมอและมีค่าที่ไม่ซ้ำกัน ตัวอย่างเช่น ในกรณีของที่เก็บออบเจ็กต์ 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 เพื่อบังคับใช้ความไม่ซ้ำกันนี้ หรือใช้ค่าที่เพิ่มขึ้นอัตโนมัติ
โค้ดต่อไปนี้จะสร้างที่เก็บออบเจ็กต์ 3 รายการซึ่งแสดงวิธีต่างๆ ในการกำหนดคีย์หลักในที่เก็บออบเจ็กต์
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();
วิธีกำหนดดัชนี
ดัชนีคือประเภทของ Object Store ที่ใช้เพื่อดึงข้อมูลจาก Object Store อ้างอิงตามพร็อพเพอร์ตี้ที่ระบุ ดัชนีจะอยู่ภายในที่เก็บออบเจ็กต์อ้างอิงและมีข้อมูลเดียวกัน แต่ใช้พร็อพเพอร์ตี้ที่ระบุเป็นเส้นทางคีย์แทนคีย์หลักของที่เก็บอ้างอิง ต้องสร้างดัชนีเมื่อคุณสร้างที่เก็บออบเจ็กต์ และสามารถใช้เพื่อกำหนดข้อจำกัดที่ไม่ซ้ำกันในข้อมูลของคุณ
หากต้องการสร้างดัชนี ให้เรียกใช้เมธอด 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() ในอินสแตนซ์ของที่เก็บออบเจ็กต์จะใช้ชื่อของดัชนีใหม่เป็นอาร์กิวเมนต์แรก และอาร์กิวเมนต์ที่ 2 จะอ้างอิงถึงพร็อพเพอร์ตี้ในข้อมูลที่คุณต้องการจัดทำดัชนี อาร์กิวเมนต์สุดท้ายช่วยให้คุณกำหนดตัวเลือก 2 รายการที่กำหนดวิธีการทำงานของดัชนี ได้แก่ 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() ในตัวแปรนั้นได้
วิธีทำงานกับข้อมูล
ส่วนนี้จะอธิบายวิธีสร้าง อ่าน อัปเดต และลบข้อมูล การดำเนินการเหล่านี้เป็นแบบอะซิงโครนัสทั้งหมด โดยใช้ Promise ในกรณีที่ IndexedDB API ใช้คำขอ ซึ่งจะช่วยให้ API ทำงานได้ง่ายขึ้น แทนที่จะคอยฟังเหตุการณ์ที่ทริกเกอร์โดยคำขอ คุณสามารถเรียกใช้ .then() ในออบเจ็กต์ฐานข้อมูลที่แสดงผลจากเมธอด openDB() เพื่อเริ่มการโต้ตอบกับฐานข้อมูล หรือ await การสร้างฐานข้อมูลได้
การดำเนินการข้อมูลทั้งหมดใน IndexedDB จะดำเนินการภายในธุรกรรม การดำเนินการแต่ละรายการมีรูปแบบดังนี้
- รับออบเจ็กต์ฐานข้อมูล
- เปิดธุรกรรมในฐานข้อมูล
- เปิดที่เก็บออบเจ็กต์ในธุรกรรม
- ดำเนินการกับที่เก็บออบเจ็กต์
ธุรกรรมอาจถือได้ว่าเป็น Wrapper ที่ปลอดภัยซึ่งครอบคลุมการดำเนินการหรือกลุ่มการดำเนินการ หากการดำเนินการอย่างใดอย่างหนึ่งภายในธุรกรรมล้มเหลว ระบบจะย้อนกลับการดำเนินการทั้งหมด ธุรกรรมจะเจาะจงสำหรับที่เก็บออบเจ็กต์อย่างน้อย 1 รายการ ซึ่งคุณกำหนดเมื่อเปิดธุรกรรม โดยอาจเป็นแบบอ่านอย่างเดียวหรือแบบอ่านและเขียนก็ได้ ซึ่งจะระบุว่าการดำเนินการภายในธุรกรรมอ่านข้อมูลหรือทำการเปลี่ยนแปลงฐานข้อมูลหรือไม่
สร้างข้อมูล
หากต้องการสร้างข้อมูล ให้เรียกใช้เมธอด add() ในอินสแตนซ์ฐานข้อมูลและส่งข้อมูลที่ต้องการเพิ่ม อาร์กิวเมนต์แรกของเมธอด add() คือที่เก็บออบเจ็กต์ที่คุณต้องการเพิ่มข้อมูล และอาร์กิวเมนต์ที่ 2 คือออบเจ็กต์ที่มีฟิลด์และข้อมูลที่เกี่ยวข้องซึ่งคุณต้องการเพิ่ม ต่อไปนี้เป็นตัวอย่างที่ง่ายที่สุดซึ่งมีการเพิ่มข้อมูลแถวเดียว
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() ในฐานข้อมูลนั้น เมธอดนี้จะรับอาร์กิวเมนต์สำหรับ Store ที่คุณต้องการทำธุรกรรม รวมถึงโหมดด้วย
ในกรณีนี้ เราสนใจที่จะเขียนไปยัง Store ดังนั้นตัวอย่างนี้จึงระบุ 'readwrite'
ขั้นตอนถัดไปคือการเริ่มเพิ่มรายการลงใน Store โดยเป็นส่วนหนึ่งของธุรกรรม
ในตัวอย่างก่อนหน้านี้ เรากำลังจัดการกับการดำเนินการ 3 รายการใน'foods'
Store ซึ่งแต่ละรายการจะแสดงผลเป็น Promise ดังนี้
- กำลังเพิ่มบันทึกสำหรับแซนด์วิชแสนอร่อย
- กำลังเพิ่มบันทึกสำหรับไข่
- การส่งสัญญาณว่าธุรกรรมเสร็จสมบูรณ์ (
tx.done)
เนื่องจากการดำเนินการทั้งหมดนี้เป็นแบบ Promise เราจึงต้องรอให้การดำเนินการทั้งหมดเสร็จสิ้น การส่ง Promise เหล่านี้ไปยัง Promise.all เป็นวิธีที่สะดวกและเหมาะสมในการดำเนินการนี้ Promise.all ยอมรับอาร์เรย์ของ Promise และจะเสร็จสิ้นเมื่อ Promise ทั้งหมดที่ส่งไปได้รับการแก้ไขแล้ว
สำหรับระเบียน 2 รายการที่เพิ่มเข้ามา อินเทอร์เฟซ store ของอินสแตนซ์ธุรกรรมจะเรียกใช้ add() และส่งข้อมูลไปให้ คุณสามารถawaitการเรียก Promise.all เพื่อให้การเรียกเสร็จสิ้นเมื่อธุรกรรมเสร็จสมบูรณ์
อ่านข้อมูล
หากต้องการอ่านข้อมูล ให้เรียกใช้เมธอด get() ในอินสแตนซ์ฐานข้อมูลที่คุณดึงข้อมูลโดยใช้เมธอด openDB()
get() จะใช้ชื่อของ Store และค่าคีย์หลักของออบเจ็กต์ที่คุณต้องการดึงข้อมูล ตัวอย่างเบื้องต้นมีดังนี้
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 ได้หากต้องการ หรือใช้ Callback .then() ของ Promise
ตัวอย่างต่อไปนี้ใช้เมธอด get() ในที่เก็บออบเจ็กต์ 'foods' ของฐานข้อมูล 'test-db4' เพื่อรับแถวเดียวตามคีย์หลัก '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() ใน Object Store เมธอด 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();
โครงสร้างของการโต้ตอบกับฐานข้อมูลจะเหมือนกับการดำเนินการอื่นๆ อย่าลืมตรวจสอบว่าธุรกรรมทั้งหมดเสร็จสมบูรณ์แล้วโดยรวมเมธอด tx.done ไว้ในอาร์เรย์ที่คุณส่งไปยัง Promise.all
กำลังดึงข้อมูลทั้งหมด
จนถึงตอนนี้ คุณได้ดึงข้อมูลออบเจ็กต์จาก Store ทีละรายการเท่านั้น นอกจากนี้ คุณยังสามารถดึงข้อมูลทั้งหมดหรือชุดย่อยจากที่เก็บออบเจ็กต์หรือดัชนีโดยใช้เมธอด 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();
เมธอดนี้จะแสดงผลออบเจ็กต์ทั้งหมดในที่เก็บออบเจ็กต์โดยไม่มีข้อจำกัดใดๆ วิธีนี้เป็นวิธีที่ตรงที่สุดในการรับค่าทั้งหมดจาก Object Store แต่ก็มีความยืดหยุ่นน้อยที่สุดด้วย
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();
ตัวอย่างนี้จะเรียก getAll() ในที่เก็บออบเจ็กต์ 'foods' ซึ่งจะแสดงผลออบเจ็กต์ทั้งหมดจาก 'foods' โดยจัดเรียงตามคีย์หลัก
วิธีใช้เคอร์เซอร์
เคอร์เซอร์เป็นวิธีที่ยืดหยุ่นกว่าในการดึงข้อมูลออบเจ็กต์หลายรายการ เคอร์เซอร์จะเลือกออบเจ็กต์แต่ละรายการในที่จัดเก็บออบเจ็กต์หรือดัชนีทีละรายการ เพื่อให้คุณดำเนินการกับข้อมูลเมื่อเลือกออบเจ็กต์นั้น เคอร์เซอร์จะทำงานในธุรกรรมเช่นเดียวกับการดำเนินการอื่นๆ ของฐานข้อมูล
หากต้องการสร้างเคอร์เซอร์ ให้เรียกใช้ openCursor() ใน Object Store เป็นส่วนหนึ่งของธุรกรรม เมื่อใช้ที่เก็บ '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 ได้ และคุณสามารถดำเนินการกับค่าเหล่านั้นในลักษณะที่เหมาะสมที่สุดสำหรับแอปของคุณ เมื่อพร้อมแล้ว คุณสามารถเรียกใช้เมธอด continue() ของออบเจ็กต์ cursor เพื่อไปยังแถวถัดไป และลูป while จะสิ้นสุดลงเมื่อเคอร์เซอร์ไปถึงจุดสิ้นสุดของชุดข้อมูล
ใช้เคอร์เซอร์กับช่วงและดัชนี
ดัชนีช่วยให้คุณดึงข้อมูลในที่จัดเก็บออบเจ็กต์ตามพร็อพเพอร์ตี้อื่นที่ไม่ใช่คีย์หลักได้ คุณสามารถสร้างดัชนีในพร็อพเพอร์ตี้ใดก็ได้ ซึ่งจะกลายเป็น keyPath สำหรับดัชนี ระบุช่วงในพร็อพเพอร์ตี้นั้น และรับข้อมูลภายในช่วงโดยใช้ getAll() หรือเคอร์เซอร์
กำหนดช่วงโดยใช้ออบเจ็กต์ IDBKeyRange และวิธีใดวิธีหนึ่งต่อไปนี้
upperBound()lowerBound()bound()(ซึ่งเป็นทั้ง 2 อย่าง)only()includes()
เมธอด upperBound() และ lowerBound() จะระบุขีดจำกัดบนและล่างของช่วง
IDBKeyRange.lowerBound(indexKey);
หรือ
IDBKeyRange.upperBound(indexKey);
โดยแต่ละฟังก์ชันจะรับอาร์กิวเมนต์ 1 รายการ ซึ่งก็คือค่า keyPath ของดัชนีสำหรับรายการที่คุณต้องการระบุเป็นขีดจำกัดบนหรือขีดจำกัดล่าง
เมธอด bound() จะระบุทั้งขีดจำกัดบนและขีดจำกัดล่าง ดังนี้
IDBKeyRange.bound(lowerIndexKey, upperIndexKey);
ช่วงสำหรับฟังก์ชันเหล่านี้จะรวมค่าเริ่มต้นไว้ด้วย ซึ่งหมายความว่าฟังก์ชันจะรวมข้อมูลที่ระบุเป็นขีดจำกัดของช่วง หากต้องการละเว้นค่าเหล่านั้น ให้ระบุช่วงเป็นไม่รวมโดยส่ง true เป็นอาร์กิวเมนต์ที่ 2 สำหรับ lowerBound() หรือ upperBound() หรือเป็นอาร์กิวเมนต์ที่ 3 และ 4 ของ bound() สำหรับขีดจำกัดล่างและขีดจำกัดบนตามลำดับ
ตัวอย่างถัดไปใช้ดัชนีในพร็อพเพอร์ตี้ 'price' ในที่เก็บออบเจ็กต์ 'foods' ตอนนี้ร้านค้ายังมีแบบฟอร์มที่แนบมาด้วยซึ่งมีอินพุต 2 รายการสำหรับขีดจำกัดบนและล่างของช่วง ใช้โค้ดต่อไปนี้เพื่อค้นหาอาหารที่มีราคาอยู่ระหว่างขีดจำกัดดังกล่าว
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' ช่วยให้คุณค้นหารายการตามราคาได้
จากนั้นโค้ดจะเปิดเคอร์เซอร์ในดัชนีและส่งผ่านช่วง เคอร์เซอร์จะแสดงผลสัญญาที่แสดงออบเจ็กต์แรกในช่วง หรือ undefined หากไม่มีข้อมูลภายในช่วง เมธอด cursor.continue() จะแสดงผลเคอร์เซอร์ที่แสดงออบเจ็กต์ถัดไป และวนซ้ำต่อไปจนกว่าจะถึงจุดสิ้นสุดของช่วง
การกำหนดเวอร์ชันฐานข้อมูล
เมื่อเรียกใช้เมธอด openDB() คุณสามารถระบุหมายเลขเวอร์ชันฐานข้อมูลในพารามิเตอร์ที่ 2 ได้ ในตัวอย่างทั้งหมดในคู่มือนี้ เวอร์ชันได้รับการตั้งค่าเป็น 1 แต่คุณสามารถอัปเกรดฐานข้อมูลเป็นเวอร์ชันใหม่ได้หากต้องการแก้ไขในลักษณะใดลักษณะหนึ่ง หากเวอร์ชันที่ระบุมากกว่าเวอร์ชันของฐานข้อมูลที่มีอยู่ ระบบจะเรียกใช้ Callback upgrade ในออบเจ็กต์เหตุการณ์ ซึ่งจะช่วยให้คุณเพิ่มที่เก็บออบเจ็กต์และดัชนีใหม่ลงในฐานข้อมูลได้
ออบเจ็กต์ db ใน Callback upgrade มีพร็อพเพอร์ตี้ oldVersion พิเศษ ซึ่งระบุหมายเลขเวอร์ชันของฐานข้อมูลที่เบราว์เซอร์มีสิทธิ์เข้าถึง
คุณสามารถส่งหมายเลขเวอร์ชันนี้ไปยังคำสั่ง switch เพื่อเรียกใช้บล็อกโค้ดภายใน Callback 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 มักจะมี break หลังบล็อก case แต่ละบล็อก แต่ในที่นี้เราจงใจไม่ใช้ ด้วยวิธีนี้ หากฐานข้อมูลที่มีอยู่เป็นเวอร์ชันเก่ากว่า 2-3 เวอร์ชัน หรือหากไม่มีอยู่ โค้ดจะดำเนินการต่อผ่านบล็อก case ที่เหลือจนกว่าจะเป็นเวอร์ชันล่าสุด ดังนั้นในตัวอย่างนี้ เบราว์เซอร์จะดำเนินการต่อผ่าน case 1 โดยสร้างดัชนี name ในที่เก็บออบเจ็กต์ store
หากต้องการสร้างดัชนี 'description' ในที่เก็บออบเจ็กต์ 'store' ให้อัปเดตหมายเลขเวอร์ชันและเพิ่มบล็อก 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 ที่มีที่เก็บออบเจ็กต์ store พร้อมดัชนี name และ description
อ่านเพิ่มเติม
แหล่งข้อมูลต่อไปนี้ให้ข้อมูลและบริบทเพิ่มเติมสำหรับการใช้ IndexedDB
เอกสารประกอบ IndexedDB
- ที่เก็บ Github
idb - การใช้ IndexedDB
- แนวคิดพื้นฐานเบื้องหลัง IndexedDB
- ข้อกำหนด Indexed Database API 3.0