ก้าวข้าม CRUD พื้นฐาน
ภาพรวมโมดูล
หัวข้อที่มีชื่อว่า “ภาพรวมโมดูล”CRUD พื้นฐานของ IndexedDB — put, get, delete — ช่วยได้มาก แต่แอปพลิเคชันจริงต้องการมากกว่านั้น โมดูลนี้ครอบคลุมเลเยอร์ขั้นสูงที่อยู่เหนือ Core API
| บทเรียน | สิ่งที่คุณจะได้เรียนรู้ |
|---|---|
| 1. ภาพรวมนี้ | Indexes, cursors, key ranges, library idb, migrations และ blobs เชื่อมต่อกันอย่างไร |
| 2. Indexes | การสร้างและสืบค้น secondary indexes; index.get vs index.getAll |
| 3. Cursors & key ranges | การเดินผ่านผลลัพธ์แบบเรียงลำดับ; IDBKeyRange.bound, lowerBound, upperBound |
| 4. library idb | Wrapper แบบ Promise; ทำไมคุณถึงควรใช้เสมอในโปรดักชัน |
| 5. Migrations | รูปแบบ onupgradeneeded สำหรับการพัฒนาสคีมาอย่างปลอดภัยข้ามเวอร์ชัน |
| 6. Blobs & files | การเก็บข้อมูลไบนารี — รูปภาพ, เสียง, PDF — โดยตรงใน object store |
Index คืออะไร?
หัวข้อที่มีชื่อว่า “Index คืออะไร?”Object store เปรียบเหมือนตารางที่มี primary key เป็นกุญแจ (โดยทั่วไปคือ id) ส่วน index คือมุมมองแบบเรียงลำดับรองของ records เดียวกัน โดยใช้คุณสมบัติอื่นเป็นกุญแจ — เช่น email หรือ createdAt
หากไม่มี index การค้นหา records ทั้งหมดที่ email === '[email protected]' ต้องเปิด cursor ผ่านทุก record (การสแกนทั้งหมด) แต่เมื่อมี index บน email เอนจินจะกระโดดไปยังรายการที่ตรงกันโดยตรงใน O(log n)
// Creating a store with an index during onupgradeneededconst store = db.createObjectStore('users', { keyPath: 'id' });store.createIndex('by_email', 'email', { unique: true });store.createIndex('by_age', 'age', { unique: false });เมื่อ index มีอยู่แล้ว คุณสามารถสืบค้นได้ภายใน transaction ใดก็ได้:
const tx = db.transaction('users', 'readonly');const index = tx.objectStore('users').index('by_email');Cursors ช่วยให้คุณเดินผ่านผลลัพธ์ได้
หัวข้อที่มีชื่อว่า “Cursors ช่วยให้คุณเดินผ่านผลลัพธ์ได้”Cursor iterate records ตามลำดับ index คุณเปิด cursor บน object store (ลำดับ primary key) หรือ index (ลำดับ secondary key) แล้วเรียก cursor.continue() เพื่อเลื่อนไปข้างหน้า:
const tx = db.transaction('users', 'readonly');const index = tx.objectStore('users').index('by_age');const range = IDBKeyRange.bound(18, 65); // ages 18–65 inclusivelet cursor = await index.openCursor(range);
while (cursor) { console.log(cursor.value.name, cursor.value.age); cursor = await cursor.continue();}Key ranges — bound, lowerBound, upperBound, only — สามารถส่งผ่านไปยังทั้ง openCursor และ getAll เพื่อกรองโดยไม่ต้องสแกนทั้งหมด
library idb
หัวข้อที่มีชื่อว่า “library idb”Raw IndexedDB API ทำงานแบบ callback/event-driven และมีความยาวมาก library idb ของ Jake Archibald ห่อ API นี้ด้วย Promises ที่สะอาดโดยไม่มีค่าใช้จ่ายรันไทม์:
import { openDB } from 'idb';
const db = await openDB('my-app', 1, { upgrade(db) { const store = db.createObjectStore('users', { keyPath: 'id' }); store.createIndex('by_email', 'email', { unique: true }); },});
บทเรียนถัดไปในโมดูลนี้ใช้ทั้ง raw IDB (เพื่อเข้าใจ primitives) และ idb (สำหรับรูปแบบโปรดักชันที่สมจริง)
Migrations ด้วย onupgradeneeded
หัวข้อที่มีชื่อว่า “Migrations ด้วย onupgradeneeded”ทุกครั้งที่คุณเพิ่มหมายเลขเวอร์ชันที่ส่งไปยัง indexedDB.open browser จะเรียก onupgradeneeded ก่อนที่ database จะเปิด นี่คือ สถานที่เดียว ที่คุณสามารถสร้างหรือลบ object stores และ indexes ได้
const db = await openDB('app', 3, { upgrade(db, oldVersion) { if (oldVersion < 1) { db.createObjectStore('users', { keyPath: 'id' }); } if (oldVersion < 2) { db.transaction.objectStore('users') .createIndex('by_email', 'email', { unique: true }); } if (oldVersion < 3) { db.createObjectStore('files', { keyPath: 'name' }); } },});บล็อก if (oldVersion < N) แบบต่อเนื่องช่วยให้ผู้ใช้สามารถอัปเกรดจากเวอร์ชันเก่าใดก็ได้ในครั้งเดียว — นี่คือรูปแบบ migration หลักที่จะอธิบายเชิงลึกในบทเรียนที่ 5
Blobs และ files
หัวข้อที่มีชื่อว่า “Blobs และ files”IndexedDB เก็บค่าที่ structured-cloneable ซึ่งรวมถึง Blob, File, ArrayBuffer และ ImageData ไม่จำเป็นต้องเข้ารหัส base64 ข้อมูลไบนารีเหมือนที่ต้องทำกับ localStorage:
const response = await fetch('/avatar.png');const blob = await response.blob();
await db.put('files', { name: 'avatar.png', data: blob, size: blob.size });
const record = await db.get('files', 'avatar.png');const url = URL.createObjectURL(record.data);// use url in an <img> srcการเก็บ blobs จะถูกครอบคลุมแบบครบวงจรในบทเรียนที่ 6
ความสัมพันธ์ระหว่าง Object store กับ Index
หัวข้อที่มีชื่อว่า “ความสัมพันธ์ระหว่าง Object store กับ Index”flowchart LR
subgraph ObjectStore["Object Store — users"]
direction TB
R1["{ id:1, name:'Ada', email:'[email protected]', age:36 }"]
R2["{ id:2, name:'Bob', email:'[email protected]', age:24 }"]
R3["{ id:3, name:'Carol', email:'[email protected]', age:31 }"]
end
subgraph PK["Primary Key (id)"]
direction TB
PK1["1 → record 1"]
PK2["2 → record 2"]
PK3["3 → record 3"]
end
subgraph IDX["Index — by_email"]
direction TB
I1["[email protected] → record 1"]
I2["[email protected] → record 2"]
I3["[email protected] → record 3"]
end
ObjectStore --> PK
ObjectStore --> IDX
IDX -. "index.get('[email protected]')" .-> R1