Object Store และ Keys
Object Store คืออะไร?
หัวข้อที่มีชื่อว่า “Object Store คืออะไร?”Object store คือสิ่งที่ IndexedDB ใช้แทนตารางใน database แต่ละ record ใน object store คือ JavaScript object ที่คุณเก็บเข้าไป — แต่ต่างจากตาราง SQL ตรงที่ไม่มีการบังคับโครงสร้าง คุณเก็บ object ที่มีรูปแบบต่างกันใน store เดียวกันได้ กฎโครงสร้างเพียงอย่างเดียวคือวิธีกำหนด key
เปรียบได้ดังนี้:
- แถวในตาราง SQL มีชุดคอลัมน์ที่ตายตัว
- record ใน object store คือ JS object ใดก็ได้ที่คุณส่งเข้าไป บวกกับ key ที่ระบุตัวตนของตัวเอง
การสร้าง Object Store
หัวข้อที่มีชื่อว่า “การสร้าง Object Store”คุณสร้าง object store โดยเรียก db.createObjectStore(name, options) บน instance ของ database method นี้ ต้อง เรียกใช้ภายใน event handler onupgradeneeded เท่านั้น — ใช้ได้เฉพาะในช่วงการอัปเกรดเวอร์ชัน
const request = indexedDB.open('my-db', 1);
request.onupgradeneeded = (event) => { const db = event.target.result; db.createObjectStore('contacts', { keyPath: 'id' });};อาร์กิวเมนต์แรกคือชื่อ store อาร์กิวเมนต์ที่สองคือ object ตัวเลือกที่ควบคุมวิธีการทำงานของ key
keyPath: In-Line Key
หัวข้อที่มีชื่อว่า “keyPath: In-Line Key”เมื่อกำหนด keyPath store จะใช้ property บน object ที่เก็บ เป็น key ซึ่งเรียกว่า in-line key เพราะ key อยู่ภายใน object นั้นเอง
// Every object you store MUST have an `id` propertydb.createObjectStore('contacts', { keyPath: 'id' });
// Valid record:
// Invalid — no `id` field, will throw:store.add({ name: 'Bob' });หากกำหนด autoIncrement: true ร่วมกับ keyPath browser จะสร้าง key ให้อัตโนมัติและเขียนลงใน object ที่ตำแหน่งที่ระบุ:
db.createObjectStore('contacts', { keyPath: 'id', autoIncrement: true });
// `id` will be filled in automatically:store.add({ name: 'Alice' }); // stored as { id: 1, name: 'Alice' }Out-of-Line Key
หัวข้อที่มีชื่อว่า “Out-of-Line Key”เมื่อไม่กำหนด keyPath (หรือตั้งเป็น null) key จะ แยกออกจาก object เรียกว่า out-of-line key คุณต้องส่ง key เองเป็นอาร์กิวเมนต์ที่สองของ add() หรือ put()
db.createObjectStore('raw-blobs', { keyPath: null });
// You provide the key explicitly:store.add({ data: '...' }, 'blob-001');ผสมกับ autoIncrement: true เพื่อให้ browser สร้าง key เป็นจำนวนเต็มโดยไม่แตะ object ที่เก็บ:
db.createObjectStore('logs', { autoIncrement: true });
// No key needed — browser assigns 1, 2, 3, ...store.add({ level: 'info', message: 'App started' });ตารางเปรียบเทียบโหมด Key
หัวข้อที่มีชื่อว่า “ตารางเปรียบเทียบโหมด Key”| โหมด | keyPath | autoIncrement | แหล่งที่มาของ Key |
|---|---|---|---|
| In-line (กำหนดเอง) | 'id' | false | ฟิลด์ id ของ object |
| In-line (อัตโนมัติ) | 'id' | true | สร้างอัตโนมัติ เก็บไว้ใน id |
| Out-of-line (กำหนดเอง) | (ไม่มี) | false | ผู้เรียกส่ง key มาเอง |
| Out-of-line (อัตโนมัติ) | (ไม่มี) | true | สร้างอัตโนมัติ ไม่อยู่ใน object |
ลองทำ: สร้าง Object Store สองอัน
หัวข้อที่มีชื่อว่า “ลองทำ: สร้าง Object Store สองอัน”ตัวอย่างด้านล่างเปิด database ที่เวอร์ชัน 1 และสร้าง store สองอัน:
contacts— in-line key โดยใช้ propertyidlogs— out-of-line key แบบ auto-increment
รันดูเพื่อดูชื่อ store ทั้งสองถูกพิมพ์ออกมาใน console
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| Schemaless object store | เก็บ object รูปแบบต่างกันใน store เดียวได้ ปรับโครงสร้างข้อมูลได้ยืดหยุ่นโดยไม่ต้อง migrate schema | ไม่มีการบังคับ validate รูปแบบ record ผิดพลาดเรื่อง field/type จะไปโผล่ตอน runtime แทน |
In-line key (keyPath) | key อยู่ใน object เดียวกัน อ่าน/debug ง่าย ไม่ต้องส่ง key แยก | object ทุกชิ้นต้องมี field ตาม keyPath เสมอ ไม่งั้น add/put error |
| Out-of-line key | แยก key ออกจาก object เก็บ object ที่ไม่มี field สำหรับ identity ได้ (เช่น blob) | ต้องส่ง key เองทุกครั้งที่เรียก add/put เพิ่ม boilerplate |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- เรียก
createObjectStore()นอกonupgradeneeded— method นี้ throwDOMExceptionทันทีถ้าเรียกนอก upgrade transaction เพราะการเปลี่ยน schema ทำได้เฉพาะช่วง version upgrade เท่านั้น - ไม่เช็ค
objectStoreNames.contains()ก่อนสร้าง store ซ้ำ — เมื่อเพิ่มเวอร์ชันแล้วonupgradeneededรันโค้ด migrate เดิมซ้ำ การเรียกcreateObjectStoreด้วยชื่อที่มีอยู่แล้วจะ throw error - สับสนว่า out-of-line key ถูกเก็บใน object — เมื่อไม่กำหนด
keyPathkey จะไม่ปรากฏใน object ที่ได้จากget()/getAll()เลย ต้องดูจากIDBCursor.keyหรือ argument ที่ส่งตอนadd()แทน
💡 ตัวอย่างจากของจริง
Notion — แยก object store ตามประเภทข้อมูล เช่น pages กับ blocks โดยใช้ in-line key
idเพื่อให้ sync กับ record บน server ตรงกันโดยตรงGoogle Docs — เก็บ edit history เป็น out-of-line key แบบ auto-increment ในแต่ละ document เพื่อรักษาลำดับเวลาของการแก้ไขโดยไม่ต้องผูก timestamp ไว้ใน object เอง