ข้ามไปยังเนื้อหา

ก้าวข้าม 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 idbWrapper แบบ Promise; ทำไมคุณถึงควรใช้เสมอในโปรดักชัน
5. Migrationsรูปแบบ onupgradeneeded สำหรับการพัฒนาสคีมาอย่างปลอดภัยข้ามเวอร์ชัน
6. Blobs & filesการเก็บข้อมูลไบนารี — รูปภาพ, เสียง, PDF — โดยตรงใน object store

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 onupgradeneeded
const 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');
const user = await index.get('[email protected]');

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 inclusive
let 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 เพื่อกรองโดยไม่ต้องสแกนทั้งหมด


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 });
},
});
await db.put('users', { id: 1, name: 'Ada', email: '[email protected]', age: 36 });
const user = await db.getFromIndex('users', 'by_email', '[email protected]');

บทเรียนถัดไปในโมดูลนี้ใช้ทั้ง raw IDB (เพื่อเข้าใจ primitives) และ idb (สำหรับรูปแบบโปรดักชันที่สมจริง)


ทุกครั้งที่คุณเพิ่มหมายเลขเวอร์ชันที่ส่งไปยัง 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


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


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
Object store พร้อม secondary index บนคุณสมบัติ email

ตัวอย่างที่รันได้: เปิด DB, สร้าง index, สืบค้นผ่าน index

หัวข้อที่มีชื่อว่า “ตัวอย่างที่รันได้: เปิด DB, สร้าง index, สืบค้นผ่าน index”
Browser Storage
สถานที่เดียวที่คุณสามารถสร้าง object store หรือ index ใหม่ใน IndexedDB คือที่ไหน?
ข้อดีหลักของ secondary index เมื่อเทียบกับการสแกน cursor ทั้งหมดคืออะไร?
เมธอด IDBKeyRange ใดที่คืนค่า range ซึ่งรวมทั้งค่าขอบเขตล่างและขอบเขตบน?
สิ่งใดต่อไปนี้ที่ IndexedDB สามารถเก็บได้โดยตรงโดยไม่ต้องเข้ารหัส base64?